Skip to content

Fix heredoc commands hanging due to indentation of terminator - #446

Merged
ghostwriternr merged 2 commits into
mainfrom
fix/heredoc-indentation
Mar 3, 2026
Merged

ghostwriternr merged 2 commits into
mainfrom
fix/heredoc-indentation

Conversation

@whoiskatrin

Copy link
Copy Markdown
Contributor

Summary

  • Fix buildCommandBlock() indenting all lines of the user command — including heredoc terminators — which caused bash to never find the terminator, hanging the command forever and locking the session mutex for all subsequent commands.

Problem

exec("cat << 'EOF'\nhello\nEOF") hangs permanently. The buildCommandBlock() function in session.ts joins the user command with shell scaffolding (env setup, exit code capture) and passes everything through indentLines(), which prepends spaces to every non-empty line. This transforms:

cat << 'EOF'
hello
EOF

into:

      cat << 'EOF'
      hello
      EOF

Bash requires heredoc terminators at column 0 (or tab-indented with <<-), so the indented EOF is never matched. The command blocks forever waiting for input, the exit code file is never written, and waitForExitCode() never resolves. Since SessionManager.executeInSession() holds the per-session mutex for the entire command duration, the mutex is never released and all subsequent commands to that session queue indefinitely.

Fix

Changed buildCommandBlock() to indent each part independently instead of indenting the joined result. The user command gets indentation only on its first line — subsequent lines (heredoc body, terminators) pass through verbatim. The env setup/cleanup blocks and exit code capture are generated code and remain safely indented.

Reviewer notes

  • The indentLines helper is unchanged — the fix is entirely in how buildCommandBlock uses it.
  • Single-line commands produce identical output to before (the indent + 2 matches the original " " + command prefix run through indentLines).
  • The 4 call sites of buildCommandBlock (lines 915, 943, 975, 986) are unchanged — they still receive a correctly indented block.

buildCommandBlock() passed the entire user command through
indentLines(), which added space prefixes to every line including
heredoc terminators. Bash requires terminators at column 0, so the
indented terminator was never matched, hanging the command and
locking the session mutex permanently.

Indent each part of the command block independently, applying
indentation only to the first line of the user command and leaving
subsequent lines verbatim.
@changeset-bot

changeset-bot Bot commented Mar 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: eabd323

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/sandbox Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenCode Review

Good fix! This correctly addresses the heredoc hanging issue by only indenting the first line of user commands, preserving bash heredoc terminator requirements. The solution is surgical and well-tested.

The changeset properly explains the user-facing benefit and the comprehensive test coverage demonstrates the fix works for various heredoc scenarios including expansion and subsequent command execution.

Looks good to merge.

@whoiskatrin

Copy link
Copy Markdown
Contributor Author

/bonk review this PR

@ask-bonk

ask-bonk Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Now I have the full picture. Let me write the review.


PR Review: Fix heredoc commands hanging due to indentation of terminator (#446)

Problem Analysis

The diagnosis is correct and well-explained. buildCommandBlock() previously joined all parts (env setup, user command, exit code capture, env cleanup) into a single string and ran indentLines() over the whole thing. This indented every non-empty line -- including heredoc terminators like EOF -- which bash requires at column 0 (or tab-indented with <<-). The result is the command hangs forever, which also holds the session mutex and blocks all subsequent commands.

Fix Analysis

The fix is sound. Instead of indent-then-join, it's join-already-indented-parts:

  1. envSetupBlock and envCleanupBlock are generated code with no user content -- safe to indent fully.
  2. ${exitVar}=$? is generated -- safe to indent fully.
  3. The user command gets only its first line indented; subsequent lines pass through verbatim.

This preserves heredoc terminators, heredoc body content, and any other multi-line construct the user might pass.

Issues

One correctness concern with indent + 2: The original code had " " + command (2 spaces) which then got indentLines(result, indent) applied, yielding indent + 2 total spaces on the command line. The new code uses ' '.repeat(indent + 2) for the first line, which matches. Good -- single-line commands produce identical output.

No issue with the heredoc body content: Lines after the first in a heredoc are literal content fed to the command's stdin. Not indenting them is correct -- the user's content should be delivered as-is.

Edge cases to consider

  1. Multi-line commands that aren't heredocs (e.g., if true; then\n echo hi\nfi) -- these now won't be indented past line 1. This is fine because the indentation is purely cosmetic (for debug readability of the generated script). The shell doesn't care about indentation of these constructs. The command runs inside { ... } redirection blocks, so structural correctness comes from the braces, not indentation.

  2. <<- (tab-stripping heredocs) -- The user could pass cat <<- 'EOF'\n\thello\n\tEOF. Since subsequent lines pass through verbatim, the tabs are preserved and bash handles it correctly. Good.

  3. Heredocs with indent=6 vs indent=4 -- Both call sites work because only the first line gets the indent prefix. The remaining lines (heredoc body/terminator) are unaffected by the indent parameter. Correct.

Tests

The 4 new tests are well-chosen:

  • Basic heredoc (quoted delimiter, no expansion)
  • Multi-line heredoc body
  • Variable expansion heredoc (unquoted delimiter)
  • Sequential command after heredoc (verifies mutex is released)

One test that would strengthen coverage: a multi-line non-heredoc command (e.g., if true; then\necho ok\nfi) to confirm those still work after this change. Not a blocker.

Changeset

The changeset correctly references @cloudflare/sandbox (not the internal container package) and the description is user-facing. Follows project conventions.

Verdict

The fix is correct, minimal, and well-tested. The root cause analysis is accurate, the solution is surgical (only changes how buildCommandBlock applies indentation, doesn't touch indentLines itself), and the PR description is thorough.

Looks good to merge.

github run

@pkg-pr-new

pkg-pr-new Bot commented Mar 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/sandbox-sdk/@cloudflare/sandbox@446

commit: eabd323

@github-actions

github-actions Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

🐳 Docker Images Published

Default:

FROM cloudflare/sandbox:0.0.0-pr-446-a695807

With Python:

FROM cloudflare/sandbox:0.0.0-pr-446-a695807-python

With OpenCode:

FROM cloudflare/sandbox:0.0.0-pr-446-a695807-opencode

Version: 0.0.0-pr-446-a695807

Use the -python variant if you need Python code execution, or -opencode for the variant with OpenCode AI coding agent pre-installed.


📦 Standalone Binary

For arbitrary Dockerfiles:

COPY --from=cloudflare/sandbox:0.0.0-pr-446-a695807 /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]

Download via GitHub CLI:

gh run download 22646179935 -n sandbox-binary

Extract from Docker:

docker run --rm cloudflare/sandbox:0.0.0-pr-446-a695807 cat /container-server/sandbox > sandbox && chmod +x sandbox

@ghostwriternr ghostwriternr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat!

@ghostwriternr
ghostwriternr merged commit bbaba54 into main Mar 3, 2026
14 checks passed
@ghostwriternr
ghostwriternr deleted the fix/heredoc-indentation branch March 3, 2026 23:06
@github-actions github-actions Bot mentioned this pull request Mar 3, 2026
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