Skip to content

serve: a host bash command's leftovers are the client's, never the model's - #271

Merged
jaredLunde merged 1 commit into
mainfrom
jared/serve-host-bash-own-jobs
Oct 16, 2026
Merged

jaredLunde merged 1 commit into
mainfrom
jared/serve-host-bash-own-jobs

Conversation

@jaredLunde

Copy link
Copy Markdown
Contributor

Fixes serve audit defect #10 (wrong output / privacy, from #253).

Problem

The host bash RPC, which is the client's command and not the model's, shared the session's job table. So a cmd & it ran was adopted as a model job. When that process exited, it woke the idle session. The wake was an unrequested, paid model turn whose input quoted the command and its output, even when the command was sent with exclude_from_context: true. The RPC result also told the client how to use bash_job, which is model-facing text.

Decision: where host-bash leftovers live

They go in a table of their own, host_bash_jobs, kept on the session:

  • Nothing takes notices from it, nothing wakes on it, and nothing in it ever reaches the model: no notice, reminder or <background-jobs> carry.
  • A transcript switch leaves it alone, because the processes are the client's, not a history's.
  • It is stopped, unreported, when the session ends.
  • Ended entries are dropped at the next host command (BashJobs::forget_ended), so the table stays bounded.
  • The RPC result says "…left X running; it runs until the session ends." An instance that never detaches has no bash_job tool, so this wording is keyed on !detaches().

build_tools now takes Option<&Arc<BashJobs>>, the client's table, in place of the bash_waits_for_exit bool. ARCHITECTURE.md ("One table per owner") documents all of this.

Tests

  • New crates/agent/tests/bash_jobs_serve_host.rs. On an idle session it runs (sleep 1; echo CLIENT-SECRET-OUTPUT) & with exclude_from_context and checks:

    • the result has no bash_job text;
    • no wake turn or background event follows;
    • no model call is made;
    • the next prompt's request carries no notice and no output.

    It then checks that a still-running host leftover dies when the session ends. The test fails on main.

  • cargo fmt, clippy --all-targets -D warnings and dprint check are clean. The agent + agent-core nextest suites passed in full, 3908/3908, on the stacked serve-jobs fixes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk

…del's

A host `bash` RPC shared the session's job table, so `cmd &` was adopted
as a model job. Its exit then woke the idle session: an unrequested, paid
model turn whose input quoted the command and its output, even with
exclude_from_context. The RPC result also carried model-facing bash_job
instructions.

- The host bash instance gets its own table (host_bash_jobs), which
  nothing takes notices from or wakes on. It survives a transcript switch
  and is stopped, unreported, when the session ends; ended entries are
  dropped at the next host command (BashJobs::forget_ended).
- build_tools takes that table instead of a bool; a Bash that never
  detaches (has no bash_job) words an adoption for its client: "it runs
  until the session ends".

Test: bash_jobs_serve_host.rs runs an excluded `(sleep 1; echo SECRET) &`
on an idle session and checks no wake, no model call, no notice in the
next prompt, then that a still-running leftover dies with the session
(fails on main).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
@jaredLunde
jaredLunde enabled auto-merge October 16, 2026 07:52
@jaredLunde
jaredLunde added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 1ad96e6 Oct 16, 2026
13 checks passed
@jaredLunde
jaredLunde deleted the jared/serve-host-bash-own-jobs branch October 16, 2026 08:20
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.

1 participant