fix(server): archive agent on initial prompt startup failure - #4013
Closed
frontend-london wants to merge 8 commits into
Closed
frontend-london wants to merge 8 commits into
frontend-london wants to merge 8 commits into
Conversation
…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>
7 tasks
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AgentManager.archiveAgent(snapshot.id)lifecycle primitive to close the native/provider session and persist the archived state.agentIdand still surfaces the original startup error.promptFailuremodes (return-error,log).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: wrapsendInitialPromptin a try/catch, best-effortarchiveAgentbefore 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=1passes (15/15).npx vitest run src/server/agent/agent-prompt.test.ts --bail=1passes (14/14).npx vitest run src/server/agent/lifecycle-command.test.ts --bail=1passes (9/9).npx vitest run src/server/agent/agent-manager.test.ts --bail=1passes (171/171).npm run typecheckinpackages/serverpasses.npm run lint -- <files>passes with 0 warnings/errors.npm run format:check:files -- <files>passes.Generated with Devin