Skip to content

fix: finalize interrupted bash via tool result path#21724

Merged
kitlangton merged 4 commits intodevfrom
followup/interrupted-bash-finalize
Apr 9, 2026
Merged

fix: finalize interrupted bash via tool result path#21724
kitlangton merged 4 commits intodevfrom
followup/interrupted-bash-finalize

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

  • let interrupted bash tool calls settle through the normal completed tool-result path before cleanup forces an aborted error
  • preserve the standard truncation behavior for interrupted bash output by finalizing late tool results through the processor
  • add regression coverage for cancelled bash output using the normal truncated tool-result path

Testing

  • bun typecheck
  • bun test --timeout 30000 test/session/prompt-effect.test.ts -t "cancel finalizes interrupted bash tool output through normal truncation"
  • bun test --timeout 30000 test/session/processor-effect.test.ts test/session/compaction.test.ts

@kitlangton kitlangton force-pushed the followup/interrupted-bash-finalize branch from 7f7a22b to 38bfedc Compare April 9, 2026 18:00
@kitlangton kitlangton force-pushed the followup/interrupted-bash-finalize branch from 38bfedc to bab098f Compare April 9, 2026 18:51
@kitlangton kitlangton added the beta label Apr 9, 2026
@kitlangton kitlangton enabled auto-merge (squash) April 9, 2026 19:06
@kitlangton kitlangton disabled auto-merge April 9, 2026 19:20
@kitlangton kitlangton merged commit 3199383 into dev Apr 9, 2026
9 of 10 checks passed
@kitlangton kitlangton deleted the followup/interrupted-bash-finalize branch April 9, 2026 19:20
timrichardson pushed a commit to timrichardson/opencode that referenced this pull request Apr 9, 2026
@pizzupy
Copy link
Copy Markdown

pizzupy commented Apr 10, 2026

@kitlangton I believe this regressed SIGPIPE handling as I've noticed Opencode closing without error/explanation. What follows is a Sonnet report. In the past this was caused by some bad scripting on my part, but it seems this time that's not the case. If the report doesn't seem bogus and I should create an issue I can do that. Environment is OSX with OpenCode running inside Docker (Ubuntu) (nothing new, been running it like this for a long while).


Since v1.4.x, running a bash command that involves a pipe where the right side closes early (e.g. | head -60) causes opencode to close instead of reporting a non-zero exit code.

Reproduction:

timeout 60s npx vue-tsc --noEmit 2>&1 | head -60

The head -60 closes the pipe after 60 lines, sending SIGPIPE to the left side. opencode exits instead of continuing.

Root cause:

In packages/opencode/src/effect/cross-spawn-spawner.ts (around line 414), when a child process exits due to a signal (code is null), the spawner returns Effect.fail(...):

exitCode: Effect.flatMap(Deferred.await(signal), ([code, signal]) => {
  if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code))
  return Effect.fail(
    toPlatformError("exitCode", new Error(`Process interrupted due to receipt of signal: '${signal}'`), command)
  )
})

In bash.ts (line 399), handle.exitCode is used inside .pipe(Effect.scoped, Effect.orDie). When exitCode fails (due to SIGPIPE), Effect.orDie converts it to a defect, crashing the fiber and taking down the whole opencode process.

SIGPIPE is normal and expected — it's not an error, it just means the consumer of a pipe stopped reading. It should be treated as exit code 141 (128 + 13) or simply as a non-zero exit.

Suggested fix:

In cross-spawn-spawner.ts, treat signal exits as a numeric exit code (e.g. map SIGPIPE → 141) rather than failing:

exitCode: Effect.flatMap(Deferred.await(signal), ([code, signal]) => {
  if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code))
  // Treat signal exits as non-zero exit codes rather than failures
  // SIGPIPE (13) → 141, SIGTERM (15) → 143, etc.
  const sigNum = signalToNumber(signal) // or just return 128 as fallback
  return Effect.succeed(ExitCode(128 + (sigNum ?? 0)))
})

Alternatively, in bash.ts, catch the failure before Effect.orDie and treat it as a non-zero exit.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants