Expand description
In-memory holding pen for a declared-background task’s OS pid when it
arrives before db_background_tasks has a row to attach it to.
agentmux-bashwrap publishes its own pid over MPS essentially at process
start (before the wrapped command even begins running) — see
bash_wrap.rs. The db_background_tasks row, by contrast, is only
created once the FRONTEND observes the tool call as an accepted
background launch (requires the literal “Command running in background
with ID:” tool-result prefix) and pushes docknodestatus, which is a
full extra round-trip through the CLI’s own stream-json processing. The
pid publish routinely wins that race.
background_task_set_pid (background_tasks.rs) is a bare
UPDATE ... WHERE id = ? — it silently no-ops (Ok(false)) when the
row doesn’t exist yet, and the pid would be lost forever with no retry
that could ever succeed (retrying the same too-early write doesn’t
help — the row still won’t exist).
set_or_stash and
observe_and_apply are the
two sides of the fix, called from websocket.rs‘s
COMMAND_BACKGROUND_TASK_PID and COMMAND_DOCK_NODE_STATUS handlers
respectively. Both run their DB call (via the try_set/apply closure)
WHILE HOLDING this module’s own lock, not as a separate step after it —
an earlier version of this module exposed plain stash/take methods
instead, with each handler doing its own DB call first and the
stash/take second, unlocked in between. That let the two handlers’
steps interleave (pid handler’s DB call sees no row → before it stashes,
the observe handler’s own take() already ran and found nothing →
observe handler’s row now exists but nothing will ever re-check it →
pid handler’s stash lands after the one take() that would have
consumed it, and sits unclaimed until the TTL drops it). Making the DB
call part of the SAME critical section as the buffer mutation closes
that window: whichever handler’s critical section runs second always
observes the first one’s effect, DB row or stash, correctly. See
docs/specs/SPEC_BACKGROUND_TASK_PID_CAPTURE_2026_08_20.md and the
Codex/reagentx findings on PR #2681 this was written to address.
Structs§
Constants§
- PENDING_
PID_ 🔒TTL_ MS - How long a stashed pid waits for its row before being dropped. Generous
relative to the observed race window (a network round-trip plus one
SQLite write, normally well under a second) but still bounded — a
declared-background call whose launch is never actually accepted as
background client-side (e.g. the command finished fast enough that
Claude’s harness returned synchronously despite
run_in_background: true— see issue #2519) would otherwise leak an entry here forever, sincebackground_task_observewould then never fire for that id.