Skip to content

fix(tools): wait for pipe readers before reaping detached bash shells#716

Merged
edenreich merged 1 commit into
mainfrom
fix/bash-detach-output-truncation
Jul 2, 2026
Merged

fix(tools): wait for pipe readers before reaping detached bash shells#716
edenreich merged 1 commit into
mainfrom
fix/bash-detach-output-truncation

Conversation

@edenreich

Copy link
Copy Markdown
Contributor

Summary

Detaching a streaming bash command to the background — chat ctrl+b, or directexec !cmd & — could truncate its trailing output. The pipe-reader goroutines drain the command's stdout/stderr into the shared shell buffer, but the live *exec.Cmd was handed to the jobs.Supervisor, whose shellJob.Run called cmd.Wait() in a separate goroutine with no coordination. Per os/exec, Wait closes the pipes on process exit, so output still buffered in the pipe at exit was lost. The time.Sleep(20ms) band-aid ran at detach time, unrelated to exit time, so it never addressed the race.

Fix

shellJob.Run now waits for the pipe readers to reach EOF before cmd.Wait(), mirroring the already-correct foreground path (wg.Wait() then cmd.Wait()). The wait selects on ctx.Done() so WindStop/Stop() can't wedge if a grandchild holds the pipe open (a detached shell has no timeout). Both detach callers thread a readers-done channel through DetachToBackground.

  • internal/domain/shell.goReadersDone on BackgroundShell; widened DetachToBackground
  • internal/services/background_shell_service.go — store readersDone on the shell
  • internal/services/jobs/shell_job.go — cancellable readers-done wait before cmd.Wait()
  • internal/agent/tools/bash.go, internal/services/directexec/bash.go — pass the signal; drop the 20ms sleep
  • regenerated fake_background_shell_service.go

Testing

  • Two regression tests in shell_job_test.go: ordering (Run parks until the readers drain, even after the process has exited) and the cancellable escape (WindStop still reaps when the readers never signal). Confirmed the latter deadlocks without the ctx.Done() case.
  • task build, go test -race on the affected packages, full task test, and task lint (0 issues) — all clean.

Closes #696

Detaching a streaming bash command (ctrl+b, or `!cmd &`) could truncate its
trailing output: the pipe-reader goroutines were still draining stdout/stderr
into the shell buffer while the supervisor reaped the process with cmd.Wait,
which closes the pipes on exit.

shellJob.Run now waits for the readers to reach EOF before cmd.Wait, selecting
on ctx so a kill or shutdown can't wedge when a grandchild is holding the pipe
open. Both detach paths thread the readers-done signal through
DetachToBackground; the old 20ms sleep band-aid is removed.
@edenreich edenreich requested a review from a team as a code owner July 2, 2026 11:46
@edenreich edenreich merged commit e1d0d84 into main Jul 2, 2026
1 check passed
@edenreich edenreich deleted the fix/bash-detach-output-truncation branch July 2, 2026 11:54
inference-gateway-releaser Bot added a commit that referenced this pull request Jul 3, 2026
## [0.128.0](v0.127.0...v0.128.0) (2026-07-03)

### 🚀 Features

* **chat:** make status indicators selectable with /tools and /a2a views ([#732](#732)) ([6882de0](6882de0)), closes [#717](#717) [#717](#717)

### 🐛 Bug Fixes

* **agent:** drop redundant Idle transition that errored on every run ([#726](#726)) ([469ec64](469ec64)), closes [#722](#722) [#725](#725)
* **agent:** report session-cumulative total_turns in completion logs ([#728](#728)) ([0d8fb08](0d8fb08)), closes [#727](#727)
* **a2a:** route active A2A reads and HasPending through the job supervisor ([#693](#693)) ([#721](#721)) ([dcd0670](dcd0670)), closes [#718](#718) [#719](#719) [#720](#720)
* **keybindings:** stop "unknown keybinding action" log flood ([#724](#724)) ([c828426](c828426)), closes [#714](#714) [#722](#722)
* **a2a:** unify background-job liveness, clear, and cancel on the supervisor contract ([#730](#730)) ([eb37fd0](eb37fd0)), closes [#693](#693) [#720](#720) [#718](#718) [#719](#719)
* **logging:** use structured key/value form at misused call sites ([#729](#729)) ([cd96028](cd96028)), closes [#722](#722) [#724](#724) [#724](#724) [#723](#723)
* **tools:** wait for pipe readers before reaping detached bash shells ([#716](#716)) ([e1d0d84](e1d0d84)), closes [#696](#696)

### ⚡ Performance Improvements

* **tui:** cache per-entry rendering and keep the Update loop responsive ([#731](#731)) ([8c9db0d](8c9db0d))

### 📚 Documentation

* **a2a:** add SKILL.md documentation for Agent2Agent protocol ([defcd57](defcd57))

### 🔧 Build System

* **deps:** bump golang.org/x/net from 0.47.0 to 0.55.0 in /examples/mcp/mcp-server ([#715](#715)) ([990803b](990803b))

### 🧹 Maintenance

* **flox:** bump dev deps ([d0c8f6b](d0c8f6b))
@inference-gateway-releaser

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.128.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Bash detach can truncate trailing output (StdoutPipe + Wait race)

1 participant