Skip to content

Give /codex:rescue a wait contract that does not depend on a background-subagent re-invocation - #106

Merged
patriyang merged 1 commit into
mainfrom
fix/99-rescue-wait-contract
Aug 11, 2026
Merged

Give /codex:rescue a wait contract that does not depend on a background-subagent re-invocation#106
patriyang merged 1 commit into
mainfrom
fix/99-rescue-wait-contract

Conversation

@patriyang

Copy link
Copy Markdown
Owner

Closes #99

Problem

plugins/codex/commands/rescue.md stated as a guarantee:

Claude Code re-invokes you when a background subagent finishes; that re-invocation is the rest of this command.

plugins/codex/commands/status.md records the opposite from observation — that re-invocation "has been observed not to arrive for subagent callers, leaving a completed Codex result unread with no signal." These are executable instructions, not prose, so /codex:rescue --background was told to end its turn and wait for a callback the project had already documented as unreliable for exactly this caller shape, with no recovery step. README.md was corrected in #97; rescue.md still promised it.

Fix — option 2 from the issue, not option 1

The issue offered documenting the recovery path (cheap) or giving rescue the same wait contract as the reviews (the real fix, "if the subagent boundary allows it"). It does: handleTask's if (options.background) branch in codex-companion.mjs already enqueues via enqueueBackgroundJob and returns the same { jobId, workspaceRoot, ... } payload the review flows consume. No runtime changes were needed.

  • The codex:codex-rescue subagent is now always invoked in the foreground. A foreground Agent call returns inside the current turn — the same property Give the background Codex flows a wait contract that does not depend on harness re-invocation #94 relied on for foreground Bash. Backgrounding the subagent is what created the failure mode, so it is gone.
  • --background now backgrounds the Codex run: the subagent enqueues with task --background --json and returns the enqueue JSON; the command reads jobId/workspaceRoot, blocks in bounded foreground steps (status … --wait --timeout-ms 240000 --json, PID-aware re-arm), then presents .storedJob.rendered verbatim in the same turn.
  • --wait / no flag: unchanged foreground behavior.
  • Recovery is now explicit: a later turn holding a dispatched-but-unread rescue recovers it from disk rather than re-dispatching.
  • rescue.md's "There is no job to poll and nothing to fetch afterwards" is corrected — a rescue's task run persists a tracked job record either way.

Changed: commands/rescue.md, agents/codex-rescue.md, skills/codex-cli-runtime/SKILL.md, README.md, plus the version bump to 1.0.43.

Verification

The regression test was written first and observed failing against main (1 fail / 31 pass), then made to pass:

  • tests/commands.test.mjs — new test rescue never depends on a background-subagent re-invocation to present its result pins both halves: the stale claims are gone, and the bounded-wait/fetch contract is present in rescue.md.
  • commands/rescue.md is now covered by the two existing cross-command contract tests — "the foreground wait mechanism is provisioned on every command that requires it" and "dispatch is never a stopping point across every async surface" — so rescue is held to the same wait contract as the review flows rather than to rescue-only assertions.
  • Assertions pinning the old contract were replaced with equally specific ones on the new wording, not loosened.
  • Full suite: 347/347 pass.

Notes

🤖 Generated with Claude Code

…nd-subagent re-invocation

`rescue.md` promised that "Claude Code re-invokes you when a background
subagent finishes", while `status.md` records that exact re-invocation as
observed not to arrive for subagent callers. These are executable
instructions, so `/codex:rescue --background` ended its turn waiting on a
callback the project had already documented as unreliable, with no
recovery step — a completed Codex run could sit unread with no signal.

`--background` now backgrounds the Codex run rather than the subagent:
the subagent is always invoked in the foreground and enqueues the tracked
job with `task --background --json`, and the command blocks on it in
bounded foreground steps, then presents the persisted result in the same
turn. That is the same contract #94 gave the review flows, and it removes
the failure mode rather than documenting it.

`task --background` already supported this; no runtime changes were
needed. Also corrects `rescue.md`'s claim that there is "nothing to fetch
afterwards" — a rescue's `task` run persists a tracked job record either
way — and the README paragraph describing the old shape.

Closes #99
@patriyang

Copy link
Copy Markdown
Owner Author

Deep review round 1/3needs-attention, one [medium] finding.

Shell-escape the workspace path before constructing wait commands (commands/rescue.md:67-69) — workspaceRoot is interpolated inside double quotes, which do not neutralize an embedded ", backtick, or $().

The finding is real but pre-existing and repo-wide, not introduced here. main already has the identical construction in all three review commands (review.md:57/64/69, adversarial-review.md:63/70/75, deep-review.md:62/69/74); this PR copies that recipe verbatim, which is the point — tests/commands.test.mjs pins the exact string across every command that carries the wait contract, so escaping it in rescue.md alone would either fail that test or leave rescue as the one flow with a divergent recipe.

Filed as #107 to fix uniformly across all four commands. Not addressed in this PR.

No other findings. Full suite green at 347/347.

@patriyang
patriyang merged commit 24ee13b into main Aug 11, 2026
3 checks passed
@patriyang
patriyang deleted the fix/99-rescue-wait-contract branch August 11, 2026 21:07
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.

rescue.md promises a background-subagent re-invocation that status.md says has been observed not to arrive

1 participant