Skip to content

fix(#2148): thread signal-aware context into sandbox exec - #2182

Merged
ggallen merged 1 commit into
mainfrom
agent/2148-thread-cancel-context
Jun 12, 2026
Merged

fix(#2148): thread signal-aware context into sandbox exec#2182
ggallen merged 1 commit into
mainfrom
agent/2148-thread-cancel-context

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

ExecStreamReader created its own context.WithTimeout from context.Background(), disconnecting it from the CLI's signal-aware context. When a workflow was cancelled during agent execution, the subprocess continued running and the status comment defer never fired, leaving it stuck at "Started".

Wire the parent context through Runtime.Run and into ExecStreamReader so SIGTERM/SIGINT cancellation propagates into the sandbox subprocess. This lets execCmd.Wait() return promptly on cancellation, allowing runAgent's defer to post the "Cancelled" status comment.

Changes:

  • sandbox.ExecStreamReader: accept ctx parameter, use as
    parent for WithTimeout instead of context.Background()
  • runtime.Runtime.Run: add ctx parameter to interface
  • runtime.ClaudeRuntime.Run: forward ctx to
    ExecStreamReader
  • cli.runAgent: pass signal-aware ctx to rt.Run

Closes #2148

Post-script verification

  • Branch is not main/master (agent/2148-thread-cancel-context)
  • Secret scan passed (gitleaks — b20e9f9cb36a09017ee59c770cd2b76378b0fdfd..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Site preview

Preview: https://540f7611-site.fullsend-ai.workers.dev

Commit: 7006fa276b4321bf13804b8338391fbd82d567f4

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:15 PM UTC · Completed 3:23 PM UTC
Commit: 86c6d54 · View workflow run →

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cli/run.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() delegates to ExecContext(context.Background(), ...) which is correct, but many call sites (bootstrap, cleanup, file upload helpers) still use Exec() with implicit context.Background(). The new ExecContext is available for future migration. The asymmetry is intentional — setup/teardown should complete even during shutdown.

  • [test-adequacy] internal/sandbox/sandbox_test.go:131TestExecContext_CancelledContext and TestExecStreamReader_OpenshellNotInPath validate error handling when openshell is not found or context is cancelled, but no test validates that parent context cancellation actually terminates a running subprocess. Writing such a test requires a real openshell binary, which is impractical in unit tests.

Previous run

Review

Findings

High

Medium

  • [logic-error] .github/workflows/reusable-dispatch.yml:155 — Removing tr -d '\r' from the SECOND_WORD extraction pipeline is unsafe. GitHub API comment bodies can contain \r\n line endings. awk '{print $2}' would yield retro\r instead of retro, causing [[ "${SECOND_WORD}" == "retro" ]] to fail and silently ignoring /fullsend retro commands from comments with Windows-style line endings. See also: [protected-path] finding on this file.
    Remediation: Keep tr -d '\r' in both pipelines, or remove these changes from this PR entirely.

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml:110 — Same tr -d '\r' removal issue in the scaffold dispatch workflow. The SECOND_WORD extraction would produce retro\r for comments with \r\n endings, breaking retro stage detection.
    Remediation: Keep tr -d '\r' in both pipelines.

Low

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() and ExecStreamReader() both now accept ctx, but many call sites (bootstrap, cleanup, file upload helpers) pass context.Background(). This is intentional — setup/teardown should complete even during shutdown — but creates an asymmetry where some long-running operations (e.g., the 60-second security scan in run.go:648) respect cancellation while structurally similar calls in bootstrap do not. Worth noting for future work.

  • [test-adequacy] internal/sandbox/sandbox_test.go:131TestExecStreamReader_OpenshellNotInPath validates error handling when openshell is not found, but no test validates that parent context cancellation actually terminates a running subprocess — the core behavior this PR introduces. Writing such a test would require a real openshell binary, which is impractical in unit tests.

  • [stale-function-signature] docs/superpowers/specs/2026-05-06-openshell-native-sandbox-transport-design.md:147 — Design spec shows old Exec signature without the ctx context.Context parameter. This is a historical design document, so impact is minimal.

Previous run (2)

Review

Findings

Low

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() still uses context.Background() while ExecStreamReader() now accepts a ctx parameter. Both run commands in the sandbox, but Exec is not signal-aware after this PR. Most Exec calls have short timeouts (5-60s) for setup/teardown, so the practical risk is low, but the inconsistency is worth noting for future work.
    Remediation: Consider adding ctx context.Context as the first parameter to Exec() in a follow-up.

  • [test-adequacy] internal/sandbox/sandbox_test.go:135TestExecStreamReader_CancelledContext sets PATH="" which causes cmd.Start() to fail with "executable not found" before context cancellation can be observed. The test verifies that ExecStreamReader returns an error with a cancelled context, but it does not validate that cancellation propagates into a running subprocess. This makes the test functionally identical to TestExecStreamReader_OpenshellNotInPath, which tests the same failure mode without a cancelled context.

Previous run

Review

Findings

Medium

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() still uses context.Background() while ExecStreamReader() now accepts a ctx parameter. Both run commands in the sandbox, but Exec is not signal-aware after this PR. Most Exec calls have short timeouts (5-60s) for setup/teardown, so the practical risk is low, but the inconsistency is worth noting for future work.
    Remediation: Consider adding ctx context.Context as the first parameter to Exec() in a follow-up, noting that most call sites can pass context.Background() initially.

  • [stale-reference] docs/superpowers/plans/2026-05-06-openshell-native-sandbox-transport.md:754 — Task 7 Step 5 "After" example still shows the old ExecStreamReader call without the ctx parameter: sandbox.ExecStreamReader(sandboxName, claudeCmd, timeout, os.Stderr). The PR updates the function signature at lines 291-295 but misses this second reference in the same file.
    Remediation: Update line 754 to sandbox.ExecStreamReader(ctx, sandboxName, claudeCmd, timeout, os.Stderr).

Low

  • [test-adequacy] internal/sandbox/sandbox_test.go:135TestExecStreamReader_CancelledContext sets PATH="" which causes cmd.Start() to fail with "executable not found" before context cancellation can be observed. The test verifies that ExecStreamReader returns an error with a cancelled context, but it does not validate that cancellation propagates into a running subprocess. Writing a true integration test would require a real openshell binary, which is impractical in unit tests.
Previous run (3)

Review

Findings

Low

  • [stale-function-signature] docs/superpowers/plans/2026-05-06-openshell-native-sandbox-transport.md:294 — The implementation plan document shows the old ExecStreamReader signature without the context.Context parameter added in this PR. Line 295 also shows context.WithTimeout(context.Background(), timeout) instead of the new context.WithTimeout(ctx, timeout). This is a historical plan document, so impact is minimal.
    Remediation: Update the ExecStreamReader function signature on line 294 to include the ctx context.Context parameter as the first parameter, and update line 295 accordingly.
Previous run (4)

Review

Findings

Low

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() still uses context.Background() while ExecStreamReader() now accepts a ctx parameter. Both run commands in the sandbox, but Exec is not signal-aware after this PR. Most Exec calls have short timeouts (5–60s) for setup/teardown, so the practical risk is low, but the inconsistency is worth noting for future work.
    Remediation: Consider adding ctx context.Context as the first parameter to Exec() in a follow-up.

  • [test-adequacy] internal/sandbox/sandbox_test.go:135TestExecStreamReader_CancelledContext sets PATH="" which causes cmd.Start() to fail with "executable not found" before context cancellation can be observed. The test verifies that ExecStreamReader returns an error with a cancelled context, but it does not validate that cancellation propagates into a running subprocess. This makes the test functionally identical to TestExecStreamReader_OpenshellNotInPath, which tests the same failure mode without a cancelled context.

Previous run (5)

Review

Findings

Medium

  • [api-consistency] internal/sandbox/sandbox.go:317Exec() still uses context.Background() while ExecStreamReader() now accepts a ctx parameter. Both run commands in the sandbox, but Exec is not signal-aware after this PR. Most Exec calls have short timeouts (5–60s) for setup/teardown, so the practical risk is low, but the inconsistency is worth noting for future work.
    Remediation: Consider adding ctx context.Context as the first parameter to Exec() in a follow-up, noting that most call sites can pass context.Background() initially.

  • [stale-reference] docs/superpowers/plans/2026-05-06-openshell-native-sandbox-transport.md:754 — Task 7 Step 5 "After" example still shows the old ExecStreamReader call without the ctx parameter: sandbox.ExecStreamReader(sandboxName, claudeCmd, timeout, os.Stderr). The PR updates the function signature at lines 291–295 but misses this second reference in the same file.
    Remediation: Update line 754 to sandbox.ExecStreamReader(ctx, sandboxName, claudeCmd, timeout, os.Stderr).

Low

  • [test-adequacy] internal/sandbox/sandbox_test.go:135TestExecStreamReader_CancelledContext sets PATH="" which causes cmd.Start() to fail with "executable not found" before context cancellation can be observed. The test verifies that ExecStreamReader returns an error with a cancelled context, but it does not validate that cancellation propagates into a running subprocess. Writing a true integration test would require a real openshell binary, which is impractical in unit tests.
Previous run (6)

Review

Findings

Low

  • [stale-function-signature] docs/superpowers/plans/2026-05-06-openshell-native-sandbox-transport.md:294 — The implementation plan document shows the old ExecStreamReader signature without the context.Context parameter added in this PR. Line 295 also shows context.WithTimeout(context.Background(), timeout) instead of the new context.WithTimeout(ctx, timeout). This is a historical plan document, so impact is minimal.
    Remediation: Update the ExecStreamReader function signature on line 294 to include the ctx context.Context parameter as the first parameter, and update line 295 accordingly.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 11, 2026
@ggallen

ggallen commented Jun 11, 2026

Copy link
Copy Markdown
Member

/fs-fix address the code coverage and also the review issues.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 4:01 PM UTC · Completed 4:09 PM UTC
Commit: 6bfb10b · View workflow run →

fullsend-ai-coder Bot added a commit that referenced this pull request Jun 11, 2026
…2182)

- Update plan document to reflect new ExecStreamReader signature with
  ctx parameter (stale-function-signature finding)
- Add unit tests for ExecStreamReader: openshell-not-in-path and
  cancelled-context error paths

Addresses review feedback on #2182
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed 2 of 3 coverage/review items: updated stale plan document signature and added ExecStreamReader unit tests. cli/run.go and runtime/claude.go coverage requires e2e tests.

Fixed (2):

  1. stale-function-signature in plan document (docs/superpowers/plans/2026-05-06-openshell-native-sandbox-transport.md): Updated ExecStreamReader signature on line 294 to include ctx context.Context parameter and changed context.Background() to ctx on line 295, matching the implementation.
  2. missing test coverage for ExecStreamReader (internal/sandbox/sandbox_test.go): Added TestExecStreamReader_OpenshellNotInPath and TestExecStreamReader_CancelledContext tests to cover the ExecStreamReader function's error paths, improving patch coverage for internal/sandbox/sandbox.go.

Disagreed (1):

  1. missing test coverage for internal/cli/run.go and internal/runtime/claude.go: The changed lines in cli/run.go (runAgent function) and runtime/claude.go (Run method) require a running sandbox with openshell to exercise the success path. These are covered by e2e tests, not unit tests. Adding a unit test that merely calls Run with PATH='' would only test the error path already covered by the ExecStreamReader test.

Tests: passed

Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:11 PM UTC · Completed 4:28 PM UTC
Commit: f1d6f52 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread internal/sandbox/sandbox_test.go
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Jun 11, 2026
@ggallen
ggallen force-pushed the agent/2148-thread-cancel-context branch from f1d6f52 to e423b9e Compare June 11, 2026 16:57
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:59 PM UTC · Completed 5:08 PM UTC
Commit: e423b9e · View workflow run →

Comment thread internal/sandbox/sandbox_test.go
@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Jun 11, 2026
@ggallen
ggallen force-pushed the agent/2148-thread-cancel-context branch from e423b9e to a0babd6 Compare June 11, 2026 17:21
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:23 PM UTC · Completed 5:37 PM UTC
Commit: a0babd6 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread .github/workflows/reusable-dispatch.yml Outdated
Comment thread internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml Outdated
Comment thread internal/sandbox/sandbox.go Outdated
Comment thread internal/sandbox/sandbox_test.go
@fullsend-ai-review fullsend-ai-review Bot removed the ready-for-merge All reviewers approved — ready to merge label Jun 11, 2026
@ggallen
ggallen force-pushed the agent/2148-thread-cancel-context branch from a0babd6 to 9d4f143 Compare June 11, 2026 17:39
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · Started 5:41 PM UTC
Commit: 9d4f143 · View workflow run →

ExecStreamReader created its own context.WithTimeout from
context.Background(), disconnecting it from the CLI's
signal-aware context. When a workflow was cancelled during
agent execution, the subprocess continued running and the
status comment defer never fired, leaving it stuck at
"Started".

Wire the parent context through Runtime.Run and into
ExecStreamReader so SIGTERM/SIGINT cancellation propagates
into the sandbox subprocess. This lets execCmd.Wait()
return promptly on cancellation, allowing runAgent's defer
to post the "Cancelled" status comment.

Changes:
- sandbox.ExecStreamReader: accept ctx parameter, use as
  parent for WithTimeout instead of context.Background()
- sandbox.ExecContext: new context-aware variant of Exec
- runtime.Runtime.Run: add ctx parameter to interface
- runtime.ClaudeRuntime.Run: forward ctx to
  ExecStreamReader
- cli.runAgent: pass signal-aware ctx to rt.Run

Closes #2148

Signed-off-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:45 PM UTC · Completed 5:54 PM UTC
Commit: 7006fa2 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 11, 2026
@ggallen
ggallen added this pull request to the merge queue Jun 12, 2026
Merged via the queue into main with commit 0de62f8 Jun 12, 2026
22 of 24 checks passed
@ggallen
ggallen deleted the agent/2148-thread-cancel-context branch June 12, 2026 11:42
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 12, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 11:46 AM UTC · Completed 12:02 PM UTC
Commit: 7006fa2 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #2182 — thread signal-aware context into sandbox exec

Workflow: Issue #2148 → triage → code → 6 reviews + 2 fix attempts → merged in ~29 hours.

What happened: The code agent produced a clean initial PR. After a human-triggered /fs-fix, the fix agent correctly addressed review findings (updated plan doc, added tests) but also made unrelated changes to two protected-path workflow files (.github/workflows/reusable-dispatch.yml and the scaffold dispatch.yml), removing tr -d '\r' calls that protect against Windows-style line endings in comment parsing. These changes would have broken /fullsend retro command detection.

Reviews 2 and 3 missed these out-of-scope workflow changes entirely, only flagging test adequacy. Review 4 finally caught the protected-path violation and requested changes. The human had to manually revert the unrelated changes. Total cost: 2 extra review cycles, 1 failed fix attempt, and human intervention.

Existing coverage: Several open issues track related improvements:

  • #2170 — escalate scope-tier-mismatch to CHANGES_REQUESTED
  • #925 — detect scope expansion after prior approval
  • #1013 — deduplicate findings across iterations
  • #1500 — don't re-request for unchanged findings

New proposal: One proposal filed for a gap not covered by existing issues — preventing the fix agent from modifying protected-path files unless explicitly directed to by review findings.

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: status comment may stay 'Started' when workflow cancelled during agent execution

2 participants