Skip to content

fix(server): archive agent on initial prompt startup failure - #4013

Closed
frontend-london wants to merge 8 commits into
getpaseo:mainfrom
frontend-london:fix/opencode-startup-orphan-cleanup
Closed

frontend-london wants to merge 8 commits into
getpaseo:mainfrom
frontend-london:fix/opencode-startup-orphan-cleanup

Conversation

@frontend-london

Copy link
Copy Markdown

Summary

  • Best-effort archive the created agent when the initial prompt fails to start, before re-throwing the startup error.
  • Uses the existing AgentManager.archiveAgent(snapshot.id) lifecycle primitive to close the native/provider session and persist the archived state.
  • Logs cleanup failures with the agentId and still surfaces the original startup error.
  • Preserves existing behavior for successful starts and non-throwing promptFailure modes (return-error, log).
  • Adds regression and negative-control tests in packages/server/src/server/agent/create-agent/create.test.ts.

Motivation

The current create-agent lifecycle creates and registers the agent snapshot before the initial prompt is sent. If the prompt startup throws (e.g. OpenCode event stream first record), the caller receives the error but the agent session can remain alive. Subsequent ACP create attempts may then produce duplicates.

Changes

  • packages/server/src/server/agent/create-agent/create.ts: wrap sendInitialPrompt in a try/catch, best-effort archiveAgent before re-throwing in throw mode.
  • packages/server/src/server/agent/create-agent/create.test.ts: add five tests covering the regression, success negative control, non-throwing modes, and cleanup-failure negative control.

Test plan

  • npx vitest run src/server/agent/create-agent/create.test.ts --bail=1 passes (15/15).
  • npx vitest run src/server/agent/agent-prompt.test.ts --bail=1 passes (14/14).
  • npx vitest run src/server/agent/lifecycle-command.test.ts --bail=1 passes (9/9).
  • npx vitest run src/server/agent/agent-manager.test.ts --bail=1 passes (171/171).
  • npm run typecheck in packages/server passes.
  • npm run lint -- <files> passes with 0 warnings/errors.
  • npm run format:check:files -- <files> passes.

Generated with Devin

frontend-london and others added 8 commits August 25, 2026 12:32
…mode routing to v0.5.2

ACP timeout behavior (76ae7d7, ee82b31):
- Bound ACP initialize with 30s timeout and session new/load/resume with 60s timeout.
- Use withTimeout and terminateChildProcess for cleanup on timeout/error.

Claude bypass resume (b7e9040):
- Track launchedMode in ClaudeAgentSession to prevent stale system/init from
  downgrading bypassPermissions to default after resume.
- Re-assert the launched mode onto the live query and add regression tests.

ACP config mode routing (8fe9063):
- Add ACPModeSource tracking (legacy/config/fallback) to resolveACPModeSelection
  and deriveModesFromACP.
- Route config-mirrored mode lists through session/set_config_option while
  legacy session modes and provider defaults still use session/set_mode.

All gates pass: format, typecheck, lint, focused ACP/Claude/managed-processes
unit tests, and build:server.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…on restart

The daemon's graceful shutdown path closed all active agents and persisted
their records as `closed`, but the bootstrap sequence never rehydrated those
records on the next startup. As a result, a `systemctl restart paseo.service`
(or any controlled restart) left previously active sessions stopped until the
user manually resumed them.

Add a minimal resume ledger (`$PASEO_HOME/resume-agents.json`):
- `bootstrap.ts:stop()` writes the list of currently live agent IDs before
  closing agents.
- `bootstrap.ts:start()` reads the ledger after the WebSocket server is ready
  and before accepting connections, then calls `resumeAgentFromPersistence`
  for each record that still has a persistence handle.
- The ledger is deleted after it is consumed so a crash (no graceful write)
  does not trigger stale resumes.

Add an E2E test in `daemon-restart-resume.e2e.test.ts` that creates a Codex
agent, stops the daemon, starts a new daemon instance against the same
`paseoHome`, and asserts the same agent ID and `sessionId` are back without a
manual `resumeAgent` call.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…CP descendant leaks

Spawn ACP provider and session children with `detached: true` so each
`devin acp` process becomes the leader of its own process group. When
closing, destroying, or cleaning up a probe, use `useProcessGroup: true`
to send signals to the entire group via `process.kill(-pid, signal)`.

This ensures that MCP descendant processes (e.g. `mcp-server-slack`)
spawned by the ACP runtime are killed alongside the parent, even when the
main child has already exited and the previous `tree-kill` fallback would
short-circuit on `already-exited`.

- Update `tree-kill.ts` to support `useProcessGroup` and kill-by-pgid.
- Set `detached: true` in `ACPAgentClient.spawnTransport` and
  `ACPAgentSession.spawnProcess`.
- Pass `useProcessGroup: true` in `terminateChildProcess`, session `close()`,
  and the child `exit` handler.
- Add regression tests for same-group cleanup and cleanup after owner exit.

Refs: agents-worker Paseo child process lifecycle fix
…cess tree

Turn failures that leave an agent in lifecycle "error" never tore down the
underlying provider session. The ACP/MCP child processes (e.g. `devin acp`
plus its MCP server children) kept running indefinitely under an agent record
that was never coming back on its own, since nothing routes "error" through
the same closeAgent teardown that archive/reload already use. On a host
running several long-lived Devin sessions this accumulated into dozens of
orphaned processes and memory pressure.

Route terminal error through AgentManager.closeAgent (same tree-kill
teardown as archive) from both places lifecycle flips to "error"
(finalizeForegroundTurn, onStreamTurnFailed). lastError and the "error"
attention reason are preserved onto the closed, resumable record, so the
failure stays visible to the user and a retry goes through the normal
ensureAgentLoaded() resume path with a fresh provider process rather than
reusing a session with no live process behind it. This matches the
already-documented `error -> closed` state in docs/agent-lifecycle.md.

Fires as a tracked background task (like other post-emitState cleanup in
this file) so a slow or already-gone process can't block the turn-finalize
path or crash the daemon; the internal error->close trigger swallows a
"runtime already gone" rejection instead of letting it surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
When `sendInitialPrompt` throws, the agent snapshot already exists but the
caller sees the startup error and the agent session can stay alive as an
orphan. ACP retries then create duplicates. Wrap the initial prompt dispatch
in `createAgentCommand` and, in throw mode, best-effort archive the created
agent before re-throwing, using `AgentManager.archiveAgent` to close the
native/provider session and persist the archived state.

If archive itself fails, log the cleanup error with the `agentId` and still
surface the original startup error. Non-throwing prompt-failure modes and
successful starts are unchanged.

Add regression and negative-control tests covering: thrown OpenCode startup
error archives the agent, success does not archive, return-error/log modes do
not archive, and cleanup failure does not mask the original error.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@boudra

boudra commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #4442 for the duplicate-after-prompt-startup-failure workflow. The canonical approach preserves the created agent and uses separate durable receipts for creation and initial-prompt delivery, so retrying cannot create another agent or blindly resend an ambiguously accepted prompt. The archive-on-failure strategy in this PR is intentionally not adopted.

@boudra boudra closed this Sep 7, 2026
@frontend-london
frontend-london deleted the fix/opencode-startup-orphan-cleanup branch September 15, 2026 17:40
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.

2 participants