Symptom
Stopping a hosted Pi agent (from the web UI or the desktop app) leaves it showing "Agent failed to start" in the web UI, even when the session ran for minutes and has a transcript.
Seen 2026-09-09 with kcap 0.11.40 / Pi 0.85.1: two hosted Pi agents stopped by their owner 6–10 minutes after launch, both logged as exited with code 137, both shown as failed. Claude and Codex agents stopped the same way exit 0 and report Completed.
Root cause
Three pieces, each in the daemon:
- The stop is a SIGKILL.
PiRpcHostedAgentRuntime.RequestGracefulStopAsync sends Pi an abort RPC — which ends the current turn, not the process — waits the 3 s stop grace, then calls TerminateAsync. IPiRpcProcess.TerminateAsync is Process.Kill(entireProcessTree: true), so the child never gets a chance to exit cleanly and the exit code is always 137.
- Non-zero exit means Failed. The orchestrator's exit handling maps
exitCode != 0 to Failed and reports AgentStatusChanged(id, "Failed", agent.SessionId). An owner-requested stop is not distinguished from a crash.
- The status report carries no session id for non-PTY runtimes.
agent.SessionId is only ever set by the PTY transcript-file match; ACP/RPC runtimes expose the canonical id through IAcpTranscriptSource.AcpSessionId, which SnapshotAgentsForStatus already falls back to but the server-facing AgentStatusChanged calls do not. The server therefore receives a failure with a null session id.
The web UI's AgentDetailView renders "Agent failed to start" exactly when the registry status is failed and the session id is null.
Fix direction
- Treat an owner-requested stop as a stop: when the stop path initiated the termination, report Completed (or a dedicated stopped status) regardless of the kill's exit code, the way
PendingEndReason already attributes other teardowns.
- Give Pi a chance to exit cleanly (a real quit, or closing stdin) before falling back to
Kill, so 137 stops being the normal outcome.
- Report the canonical session id for non-PTY runtimes on every
AgentStatusChanged, falling back to IAcpTranscriptSource.AcpSessionId as the status snapshot does.
Related: #839.
Symptom
Stopping a hosted Pi agent (from the web UI or the desktop app) leaves it showing "Agent failed to start" in the web UI, even when the session ran for minutes and has a transcript.
Seen 2026-09-09 with kcap 0.11.40 / Pi 0.85.1: two hosted Pi agents stopped by their owner 6–10 minutes after launch, both logged as
exited with code 137, both shown as failed. Claude and Codex agents stopped the same way exit 0 and report Completed.Root cause
Three pieces, each in the daemon:
PiRpcHostedAgentRuntime.RequestGracefulStopAsyncsends Pi anabortRPC — which ends the current turn, not the process — waits the 3 s stop grace, then callsTerminateAsync.IPiRpcProcess.TerminateAsyncisProcess.Kill(entireProcessTree: true), so the child never gets a chance to exit cleanly and the exit code is always 137.exitCode != 0toFailedand reportsAgentStatusChanged(id, "Failed", agent.SessionId). An owner-requested stop is not distinguished from a crash.agent.SessionIdis only ever set by the PTY transcript-file match; ACP/RPC runtimes expose the canonical id throughIAcpTranscriptSource.AcpSessionId, whichSnapshotAgentsForStatusalready falls back to but the server-facingAgentStatusChangedcalls do not. The server therefore receives a failure with a null session id.The web UI's
AgentDetailViewrenders "Agent failed to start" exactly when the registry status isfailedand the session id is null.Fix direction
PendingEndReasonalready attributes other teardowns.Kill, so 137 stops being the normal outcome.AgentStatusChanged, falling back toIAcpTranscriptSource.AcpSessionIdas the status snapshot does.Related: #839.