pi-mono/packages
robopipi[bot] 1708b47889
fix(coding-agent): process idle ctx.shutdown() requests in RPC mode (#11746)
In RPC mode, `shutdownHandler` only set a `shutdownRequested` flag. That
flag was checked in two places: after a stdin command was handled and on
`agent_settled`. If an extension called `ctx.shutdown()` while the
session was idle (for example from a timer), neither check ever ran. The
process stayed alive until the client sent another command. Interactive
mode already shuts down right away in this case.

Changes in `packages/coding-agent/src/modes/rpc/rpc-mode.ts`:
- **One check for all paths:** `checkShutdownRequested()` shuts down
only when a request is pending, input is attached, no command is being
handled, and the session is idle. Each of those ending calls it again:
command completion, prompt settle, `agent_settled`, and input attach.
- **Idle requests:** the shutdown handler schedules the check on the
next tick, so the requesting handler finishes first. The conditions are
checked when the tick runs, not when it is scheduled: a stdin command
that arrives in between is handled first, and shutdown follows when it
finishes.
- **Requests during startup:** a request made before input is attached
(for example a synchronous `ctx.shutdown()` in `session_start`) is
checked once the input reader is attached.
- **Requests during a command:** shutdown waits until the command
finishes, including extension commands. A `prompt` command returns
before `session.prompt()` settles, so it stays counted as in flight
until the prompt settles, and the shutdown check runs then. This also
fixes an existing ordering problem: an extension command that called
`ctx.shutdown()` could exit the process before its `prompt` response was
written.
- **Repeated requests:** `checkShutdownRequested()` does nothing once
shutdown has started, so a second `ctx.shutdown()` does not call
`process.exit` in the middle of async cleanup. A second stdin EOF or
signal still forces exit, as before.

Shutdown requested during a running turn is still handled on
`agent_settled`, as before. A request made while an extension-started
compaction runs (not idle, no run to settle) still waits for the next
command or run; `docs/rpc.md` and `docs/extensions.md` note this.

Docs (`docs/rpc.md`, `docs/extensions.md`) and the CHANGELOG are
updated.

## Testing
- Added three tests to `test/rpc-prompt-response-semantics.test.ts`:
  - an async `ctx.shutdown()` on an idle session exits;
- an extension command that calls `ctx.shutdown()` gets its `prompt`
response written before exit;
- a command that arrives after an idle request but before the scheduled
check runs gets its response written before exit.

  All three fail without the change and pass with it.
- `npm run check` passes. All `test/rpc*` tests pass.
- In `./test.sh`, all coding-agent tests pass. The only failures were
timeouts in durable/harness tests (`store-storage`, `tools-images`,
connection close), which this change does not touch.
- Reproduction from the issue: run `pi --mode rpc --no-session -ne -e
./shutdown-probe.mjs` with stdin held open and nothing sent.
- Before the change, the process stayed alive until a timeout killed it
(exit 124).
- After the change, `session_shutdown` fires and the process exits with
code 0.
- A synchronous `ctx.shutdown()` in `session_start` also exits with code
0.

Fixes #11599

Requested by @petrroll

---------

Co-authored-by: triagent <triagent@localhost>
Co-authored-by: Petr Houška <houskape@gmail.com>
2026-11-03 21:27:14 +01:00
..
agent Anthropic message threads (#11545) 2026-11-03 14:07:57 +01:00
ai Anthropic message threads (#11545) 2026-11-03 14:07:57 +01:00
chord feat(chord): messages and languages in the app kit (i18n) 2026-11-03 20:25:53 +00:00
codemode Add [Unreleased] section for next cycle 2026-11-02 11:38:21 +01:00
coding-agent fix(coding-agent): process idle ctx.shutdown() requests in RPC mode (#11746) 2026-11-03 21:27:14 +01:00
durable feat(chord): Chord v2 is the package's root 2026-11-02 22:36:55 +00:00
env fix(env): kill what a process program left running when it exits 2026-11-03 14:17:21 +01:00
evals Release v1.5.0 2026-11-02 11:38:21 +01:00
mcp fix(mcp): cancel SSE response streams that fail the message-size limit (#11735) 2026-11-03 14:22:16 +01:00
schema Add [Unreleased] section for next cycle 2026-11-02 11:38:21 +01:00
telemetry Add [Unreleased] section for next cycle 2026-11-02 11:38:21 +01:00
tui Add [Unreleased] section for next cycle 2026-11-02 11:38:21 +01:00