Skip to content

fix(dynamic-blocks): tee stdout/stderr so print() still reaches Docke…#2258

Open
rafel-roboflow wants to merge 6 commits intomainfrom
feat/dg-464-custom-python-block-print-output-no-longer-reaches-docker
Open

fix(dynamic-blocks): tee stdout/stderr so print() still reaches Docke…#2258
rafel-roboflow wants to merge 6 commits intomainfrom
feat/dg-464-custom-python-block-print-output-no-longer-reaches-docker

Conversation

@rafel-roboflow
Copy link
Copy Markdown
Contributor

@rafel-roboflow rafel-roboflow commented Apr 22, 2026

What does this PR do?

Restores print() output from Custom Python blocks to Docker / process stdout.

_ThreadDispatchStream in error_utils.py was redirecting writes to per-thread StringIO buffers that were only read on error, silently swallowing output on the happy path. Writes are now teed to both the capture buffer (for DynamicBlockCodeError.stdout/stderr) and the original stream.

Related Issue(s): Fixes DG-464

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:
Ran a workflow with a Custom Python block containing print(...) in local execution mode; output now appears in docker logs again, and captured stdout/stderr is still attached to DynamicBlockCodeError when the block raises.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Additional Context

N/A


Note

Medium Risk
Changes global stdout/stderr capturing for dynamic blocks and alters remote-execution response payloads, which could affect logging behavior and error reporting across workflow runs.

Overview
Restores visible print() output for Custom Python workflow blocks by changing capture_output() to tee writes to both the per-thread buffer (for error context) and the original sys.stdout/sys.stderr.

For Modal-based execution, the remote sandbox now returns captured stdout/stderr, and the client (ModalExecutor) forwards those streams to the local process logs on success.

Makes WorkflowErrorResponse.inner_error_type, inner_error_message, and blocks_errors optional (defaulting to None) to avoid requiring these fields in error responses.

Reviewed by Cursor Bugbot for commit cbffe85. Bugbot is set up for automated code reviews on this repo. Configure here.

Make `inner_error_type` and `inner_error_message` optional on
`WorkflowErrorResponse` so errors raised without an `inner_error`
(e.g. from the Modal/OCI executors) don't fail pydantic validation
and surface as a 500 instead of the intended 400.
stdout = result.get("stdout")
stderr = result.get("stderr")
if stdout:
sys.stdout.write(stdout)
sys.stdout.write(stdout)
sys.stdout.flush()
if stderr:
sys.stderr.write(stderr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants