Skip to content

feat(desktop,host-service): surface agent failures as a "failed" status - #5697

Merged
Kitenite merged 4 commits into
mainfrom
failure-detection-hooks
Jul 15, 2026
Merged

Kitenite merged 4 commits into
mainfrom
failure-detection-hooks

Conversation

@Kitenite

@Kitenite Kitenite commented Jul 14, 2026 •

Copy link
Copy Markdown
Member

Problem

Terminal-agent status was derived from a Start / Stop / PermissionRequest vocabulary with no failure category, so a failed agent turn was silent — the pane stayed on "working".

What this does

Adds a first-class Failed lifecycle event and a red failed PaneStatus, driven purely by CLI-reported failures: Claude Code emits a StopFailure hook on API errors (rate_limit, overloaded, authentication_failed, billing_error, server_error, …). We register that hook and map StopFailure → Failed.

Because StopFailure fires while the session stays alive, the binding just flips to Failed and surfaces through the normal live read — no session-status or liveness plumbing. It self-clears when the agent retries (next Start). The failed status is a red pulsing dot (like permission) and counts toward the unread / dock-badge attention totals.

Scope

Deliberately limited to failures a CLI actually reports via a hook — no exit-code/crash inference (that path had false-positive risk on ordinary non-zero shell exits and is not worth the complexity). Non-Claude CLIs that don't emit a failure hook simply won't show failed; that's fine.

Tests

mapEventType (StopFailure→Failed), deriveTerminalAgentStatus, the store recording Failed without dropping the binding, and the full StopFailure→Failed notifications-router path (asserting agent identity survives). Typecheck + lint clean.

Verified end-to-end (CDP)

Drove the running dev desktop: POSTing a StopFailure hook for a live terminal agent flipped the binding Start→Failed and turned the Terminal tab, pane header, and sidebar workspace badge from amber "working" to red "failed".


Open in Stage

Summary by cubic

Surfaces agent failures as a first-class failed status across apps/desktop and packages/host-service. Claude Code API errors now show a red “failed” state instead of staying “working”.

  • New Features

    • Added Failed lifecycle event and failed PaneStatus (red, pulsing). Shown in status indicators and attention/unread counts; priority just below permission.
    • Register Claude Code StopFailure hook and map to Failed while the session stays alive; clears on next Start. Detection is hook-reported only (no exit-code heuristics).
    • Keep failed bindings in live reads so panes show failure; never use them as attach targets.
  • Bug Fixes

    • Preserve agent identity on Failed broadcasts and keep the binding in the store.

Written for commit 505a571. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added clear failure status tracking for terminal agents.
    • Failure events now remain visible and display a red, pulsing “Agent failed” indicator.
    • Workspaces with failed agents are marked as requiring attention and included in attention counts.
    • Claude integrations now report stop-failure events.
  • Bug Fixes

    • Distinguished failed agent runs from normally stopped runs, ensuring failure notifications and statuses are surfaced correctly.

Terminal-agent status was derived from a Start/Stop/PermissionRequest
vocabulary with no failure category, so API failures were silent: an
error that kept the process alive left the pane stuck on "working", and
a crash silently dropped the binding to idle.

Add a first-class "failed" lifecycle event and PaneStatus (red, styled
like "permission"), fed by two detectors:

- Claude Code StopFailure hook (primary): fires on API errors
  (rate_limit, overloaded, auth, billing, server_error, ...) while the
  session stays alive. Registered in the Claude wrapper config and
  mapped StopFailure -> Failed; the binding flips to failed and
  self-clears when the agent retries.
- Abnormal process exit (universal fallback): the pty onExit now
  classifies non-zero/crash-signal exits (excluding clean exits and
  user interrupts) and, when an agent was bound, marks the terminal
  "failed" so the binding survives the liveness filter instead of
  silently vanishing.

The failed session status is surfaced by the live binding reads (but
never as an attach target), flows through StatusIndicator, the status
priority aggregation, and the dock-badge/unread counts.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Agent failure events are registered, normalized, persisted, and broadcast as Failed. Desktop status derivation, indicators, unread state, and attention counts now surface failed agents.

Changes

Agent failure lifecycle

Layer / File(s) Summary
Failure event contracts and hook routing
packages/host-service/src/events/map-event-type.ts, packages/host-service/src/events/map-event-type.test.ts, apps/desktop/src/main/lib/agent-setup/...
Claude StopFailure hooks are registered, and failure event variants normalize to the Failed lifecycle type.
Failure state persistence and notification validation
packages/host-service/src/trpc/router/notifications/notifications.test.ts, packages/host-service/src/terminal-agents/store.test.ts
Tests verify failed notifications preserve agent identity and update existing terminal bindings without deleting them.
Desktop failed status and attention state
apps/desktop/src/shared/tabs-types.ts, apps/desktop/src/renderer/hooks/host-service/useTerminalAgentStatuses/*, apps/desktop/src/renderer/hooks/host-service/useV2NotificationStatus/*, apps/desktop/src/renderer/screens/main/components/StatusIndicator/*
The failed pane status is prioritized, derived from Failed events, shown with a red pulsing indicator, and included in unread and attention calculations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Claude
  participant NotificationsRouter
  participant HostService
  participant Desktop
  Claude->>NotificationsRouter: Send StopFailure hook
  NotificationsRouter->>HostService: Normalize and store Failed event
  HostService-->>Desktop: Broadcast failed terminal status
  Desktop-->>Desktop: Show failure indicator and attention state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main change: surfacing agent failures as a failed status.
Description check ✅ Passed The description covers the problem, solution, scope, and testing, though it does not follow the template headings exactly and omits the checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch failure-detection-hooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ghost

ghost commented Jul 14, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Define Failed lifecycle event and PaneStatus
2 Map CLI failure hooks to Failed event
3 Update host service to persist failure state
4 Derive and display failed status in UI
5 Surface failure in workspace notifications
Open in Stage

Chapters generated by Stage for commit 505a571 on Jul 14, 2026 11:52pm UTC.

@github-actions

github-actions Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files

Architecture diagram
sequenceDiagram
    participant Claude as Claude Code CLI
    participant Host as Host Service
    participant Router as Event Router
    participant Store as Agent Store & DB
    participant Pty as PTY Session
    participant Exit as Exit Analyzer
    participant Bus as Event Bus
    participant UI as UI Renderer
    participant Attach as Attach API

    Note over Claude,UI: Agent Failure Flow

    %% Claude StopFailure path
    Claude->>Host: POST /hook { eventType: "StopFailure", ... }
    Host->>Router: route hook
    Router->>Router: mapEventType("StopFailure") → "Failed"
    Router->>Store: recordEvent(terminalId, eventType="Failed")
    Store->>Store: update binding lastEventType="Failed"
    Store-->>Router: ok
    Router->>Bus: broadcastAgentLifecycle(Failed)
    Bus-->>UI: lifecycle event (Failed)

    %% UI handling
    UI->>UI: deriveTerminalAgentStatus(...) → "failed"
    UI->>UI: StatusIndicator: red pulsing dot
    UI->>UI: include in attention workspace count

    %% Abnormal exit path
    Pty->>Pty: onExit(code, signal)
    Pty->>Exit: isAbnormalAgentExit(code, signal)
    alt Abnormal exit (crash, non-zero, not user interrupt)
        Exit-->>Pty: true
        Pty->>Store: markAgentBindingFailed(terminalId, occurredAt)
        Store->>Store: update binding lastEventType="Failed"
        Store-->>Pty: ok
        Pty->>Store: update session status="failed"
        Pty->>Bus: broadcastAgentLifecycle(Failed)
        Bus-->>UI: lifecycle event (Failed)
        UI->>UI: same as above
    else Normal exit (clean, user interrupt)
        Exit-->>Pty: false
        Pty->>Store: update session status="exited"
    end

    Note over Store,Attach: Failed bindings kept in listLiveByWorkspace,<br>but attach API rejects them
    Attach->>Store: findLiveActive(workspace, agent)
    alt status == "failed"
        Attach-->>Client: error "terminal has exited"
    else status == "active"
        Attach-->>Client: attach success
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a first-class failed agent status surfaced by two detection paths: the Claude Code StopFailure in-band hook (API errors while the session is alive) and an abnormal process exit detector (isAbnormalAgentExit) that catches crashes and non-zero exits for any CLI. The failed state persists like permission, contributes to unread/attention counts, and is shown as a red pulsing dot.

  • In-band path (StopFailure hook): routes cleanly through mapEventType → store.recordEvent(\"Failed\"), keeping both the in-memory store and the DB consistent, and self-clears when the user retries (new Start event).
  • Out-of-band path (abnormal exit): markAgentBindingFailed writes directly to the DB without going through TerminalAgentStore.recordEvent(), so store.get(terminalId) will return stale pre-crash data for the rest of the host lifetime.
  • USER_INTERRUPT_EXIT_CODES only covers exit codes 130 and 143, omitting 129 (SIGHUP) and 137 (SIGKILL), which could over-classify some shell-wrapper exits as failures.

Confidence Score: 3/5

The core failed-state machinery is sound for the in-band StopFailure path; the abnormal-exit path has an undocumented state divergence in the in-memory store that could produce wrong answers from store.get() after a crash.

The abnormal-exit detection in terminal.ts writes the failure state directly to the DB without updating TerminalAgentStore's in-memory byTerminal Map. The new store test only exercises the recordEvent path, not the markAgentBindingFailed DB-direct path, leaving the divergence untested. Additionally, USER_INTERRUPT_EXIT_CODES misses exit code variants for SIGHUP (129) and SIGKILL (137). The PR explicitly notes it has not been end-to-end verified in the running app.

packages/host-service/src/terminal/terminal.ts (markAgentBindingFailed bypasses in-memory store) and packages/host-service/src/terminal/agent-exit.ts (missing exit code variants 129 and 137)

Important Files Changed

Filename Overview
packages/host-service/src/terminal/terminal.ts Adds markAgentBindingFailed that writes directly to the DB without updating TerminalAgentStore's in-memory byTerminal Map, creating a state divergence where store.get() returns stale data after abnormal exits
packages/host-service/src/terminal/agent-exit.ts New utility classifying abnormal pty exits; logic is mostly correct but USER_INTERRUPT_EXIT_CODES only covers SIGINT (130) and SIGTERM (143), omitting SIGHUP (129) and SIGKILL (137) shell-encoding variants
packages/host-service/src/terminal-agents/persistence.ts Correctly extends listLiveByWorkspace and listLive to include 'failed' sessions while keeping findLiveActive restricted to 'active' only
packages/host-service/src/events/map-event-type.ts Adds Failed lifecycle event type with correct ordering (failure check before Stop); maps StopFailure and multiple aliases correctly
apps/desktop/src/shared/tabs-types.ts Adds 'failed' PaneStatus; getHighestPriorityStatus early-exit on 'permission' is still correct; acknowledgedStatus leaves 'failed' uncleared (intentional, like permission)
packages/host-service/src/terminal-agents/store.test.ts New test verifies Failed event is recorded on binding via recordEvent(), but does not cover the markAgentBindingFailed DB-direct path used in the abnormal-exit code path
apps/desktop/src/renderer/hooks/host-service/useV2NotificationStatus/useV2NotificationStatus.ts Correctly includes 'failed' in unread and attention-count checks
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx Adds red pulsing 'failed' indicator config, styled consistently with 'permission'
packages/host-service/src/trpc/router/notifications/notifications.test.ts Good coverage of StopFailure to Failed routing through the notification hook path
apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts Registers StopFailure hook correctly alongside Stop using the same managedHookCommand

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as Claude Code CLI
    participant NR as Notifications Router
    participant Store as TerminalAgentStore
    participant DB as SQLite DB
    participant EB as EventBus
    participant UI as Renderer

    Note over CLI,UI: Path 1 - In-band StopFailure
    CLI->>NR: "POST /hook {eventType: StopFailure}"
    NR->>Store: "recordEvent({eventType: Failed})"
    Store->>DB: "upsert binding lastEventType=Failed"
    NR->>EB: broadcastAgentLifecycle(Failed)
    EB->>UI: refresh - listByWorkspace shows failed

    Note over CLI,UI: Path 2 - Abnormal process exit
    CLI->>DB: "markAgentBindingFailed - lastEventType=Failed"
    CLI->>DB: "terminalSessions.status=failed"
    CLI->>EB: broadcastAgentLifecycle(Failed)
    EB->>UI: refresh - listByWorkspace shows failed
    Note over Store: store.get() returns stale pre-crash binding
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as Claude Code CLI
    participant NR as Notifications Router
    participant Store as TerminalAgentStore
    participant DB as SQLite DB
    participant EB as EventBus
    participant UI as Renderer

    Note over CLI,UI: Path 1 - In-band StopFailure
    CLI->>NR: "POST /hook {eventType: StopFailure}"
    NR->>Store: "recordEvent({eventType: Failed})"
    Store->>DB: "upsert binding lastEventType=Failed"
    NR->>EB: broadcastAgentLifecycle(Failed)
    EB->>UI: refresh - listByWorkspace shows failed

    Note over CLI,UI: Path 2 - Abnormal process exit
    CLI->>DB: "markAgentBindingFailed - lastEventType=Failed"
    CLI->>DB: "terminalSessions.status=failed"
    CLI->>EB: broadcastAgentLifecycle(Failed)
    EB->>UI: refresh - listByWorkspace shows failed
    Note over Store: store.get() returns stale pre-crash binding
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/shared/tabs-types.ts, line 91-94 (link)

    P2 The acknowledgedStatus comment doesn't document failed, leaving its persistence behavior implicit. Since failed is intentionally designed to persist like permission (it clears only on retry or terminal close, not on acknowledgement), the omission may lead a future reader to accidentally add a clearing case.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/shared/tabs-types.ts
    Line: 91-94
    
    Comment:
    The `acknowledgedStatus` comment doesn't document `failed`, leaving its persistence behavior implicit. Since `failed` is intentionally designed to persist like `permission` (it clears only on retry or terminal close, not on acknowledgement), the omission may lead a future reader to accidentally add a clearing case.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/host-service/src/terminal/terminal.ts:906-922
**`markAgentBindingFailed` bypasses the in-memory store cache**

`markAgentBindingFailed` writes `lastEventType = "Failed"` directly to the DB without going through `TerminalAgentStore.recordEvent()`. The store's `listByWorkspace`, `list`, and `findActive` delegate to the persistence layer, so those reads correctly reflect the failure. However, `store.get(terminalId)` reads solely from the in-memory `byTerminal` Map and will return the stale pre-crash binding (e.g. `lastEventType: "Start"`) for the rest of the host-service process lifetime. The function's own doc comment says "the store's reads are persistence-backed," but `get()` is explicitly not — it's the fresh-launch wait path. For the in-band `StopFailure` route, `recordEvent("Failed")` keeps both layers consistent. The abnormal-exit route has no equivalent, creating an undocumented divergence. If any code calls `store.get(terminalId)` after an abnormal exit and branches on `lastEventType`, it will observe wrong state.

### Issue 2 of 3
packages/host-service/src/terminal/agent-exit.ts:3
`USER_INTERRUPT_EXIT_CODES` only covers SIGINT (130) and SIGTERM (143), but omits the SIGHUP (129) and SIGKILL (137) shell-encoding variants. A bash/zsh wrapper that catches SIGHUP or is killed by the OOM killer's final SIGKILL while an inner process handles the signal and re-exits with `128 + signal` would report `code = 129` or `code = 137` with `signal = 0`. The function currently classifies both as abnormal failures even though the corresponding signals (1 and 9) are in `USER_INTERRUPT_SIGNALS`. Symmetric coverage prevents this over-classification.

```suggestion
const USER_INTERRUPT_EXIT_CODES = new Set([129, 130, 137, 143]);
```

### Issue 3 of 3
apps/desktop/src/shared/tabs-types.ts:91-94
The `acknowledgedStatus` comment doesn't document `failed`, leaving its persistence behavior implicit. Since `failed` is intentionally designed to persist like `permission` (it clears only on retry or terminal close, not on acknowledgement), the omission may lead a future reader to accidentally add a clearing case.

```suggestion
 * - "review"     → "idle"    (user saw the completion)
 * - "permission" → unchanged (persists until agent resumes)
 * - "failed"     → unchanged (persists until agent retries or terminal closes)
 * - "working"    → unchanged (persists until agent stops)
 * - "idle"       → unchanged
```

Reviews (1): Last reviewed commit: "feat(desktop,host-service): surface agen..." | Re-trigger Greptile

Comment on lines +906 to +922
function markAgentBindingFailed(
db: HostDb,
terminalId: string,
occurredAt: number,
): boolean {
const binding = db.query.terminalAgentBindings
.findFirst({
where: eq(terminalAgentBindings.terminalId, terminalId),
columns: { terminalId: true },
})
.sync();
if (!binding) return false;
db.update(terminalAgentBindings)
.set({ lastEventType: "Failed", lastEventAt: occurredAt })
.where(eq(terminalAgentBindings.terminalId, terminalId))
.run();
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 markAgentBindingFailed bypasses the in-memory store cache

markAgentBindingFailed writes lastEventType = "Failed" directly to the DB without going through TerminalAgentStore.recordEvent(). The store's listByWorkspace, list, and findActive delegate to the persistence layer, so those reads correctly reflect the failure. However, store.get(terminalId) reads solely from the in-memory byTerminal Map and will return the stale pre-crash binding (e.g. lastEventType: "Start") for the rest of the host-service process lifetime. The function's own doc comment says "the store's reads are persistence-backed," but get() is explicitly not — it's the fresh-launch wait path. For the in-band StopFailure route, recordEvent("Failed") keeps both layers consistent. The abnormal-exit route has no equivalent, creating an undocumented divergence. If any code calls store.get(terminalId) after an abnormal exit and branches on lastEventType, it will observe wrong state.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/terminal/terminal.ts
Line: 906-922

Comment:
**`markAgentBindingFailed` bypasses the in-memory store cache**

`markAgentBindingFailed` writes `lastEventType = "Failed"` directly to the DB without going through `TerminalAgentStore.recordEvent()`. The store's `listByWorkspace`, `list`, and `findActive` delegate to the persistence layer, so those reads correctly reflect the failure. However, `store.get(terminalId)` reads solely from the in-memory `byTerminal` Map and will return the stale pre-crash binding (e.g. `lastEventType: "Start"`) for the rest of the host-service process lifetime. The function's own doc comment says "the store's reads are persistence-backed," but `get()` is explicitly not — it's the fresh-launch wait path. For the in-band `StopFailure` route, `recordEvent("Failed")` keeps both layers consistent. The abnormal-exit route has no equivalent, creating an undocumented divergence. If any code calls `store.get(terminalId)` after an abnormal exit and branches on `lastEventType`, it will observe wrong state.

How can I resolve this? If you propose a fix, please make it concise.

@@ -0,0 +1,19 @@
// Exit codes for user-initiated interrupts (128 + SIGINT/SIGTERM) — the user
// stopped the agent, not a failure.
const USER_INTERRUPT_EXIT_CODES = new Set([130, 143]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 USER_INTERRUPT_EXIT_CODES only covers SIGINT (130) and SIGTERM (143), but omits the SIGHUP (129) and SIGKILL (137) shell-encoding variants. A bash/zsh wrapper that catches SIGHUP or is killed by the OOM killer's final SIGKILL while an inner process handles the signal and re-exits with 128 + signal would report code = 129 or code = 137 with signal = 0. The function currently classifies both as abnormal failures even though the corresponding signals (1 and 9) are in USER_INTERRUPT_SIGNALS. Symmetric coverage prevents this over-classification.

Suggested change
const USER_INTERRUPT_EXIT_CODES = new Set([130, 143]);
const USER_INTERRUPT_EXIT_CODES = new Set([129, 130, 137, 143]);
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/terminal/agent-exit.ts
Line: 3

Comment:
`USER_INTERRUPT_EXIT_CODES` only covers SIGINT (130) and SIGTERM (143), but omits the SIGHUP (129) and SIGKILL (137) shell-encoding variants. A bash/zsh wrapper that catches SIGHUP or is killed by the OOM killer's final SIGKILL while an inner process handles the signal and re-exits with `128 + signal` would report `code = 129` or `code = 137` with `signal = 0`. The function currently classifies both as abnormal failures even though the corresponding signals (1 and 9) are in `USER_INTERRUPT_SIGNALS`. Symmetric coverage prevents this over-classification.

```suggestion
const USER_INTERRUPT_EXIT_CODES = new Set([129, 130, 137, 143]);
```

How can I resolve this? If you propose a fix, please make it concise.

…ailed test coverage, doc accuracy

- isAbnormalAgentExit: treat the 128+signal shell encodings (129 SIGHUP,
  137 SIGKILL) as user-interrupt, symmetric with USER_INTERRUPT_SIGNALS,
  so both encodings classify identically (greptile P2).
- notifications StopFailure test: assert agent identity survives onto the
  Failed broadcast and the store binding (cubic P3).
- markAgentBindingFailed: document that the direct-DB write can leave
  store.get() briefly stale, why its sole caller is unaffected, and when
  the entry clears (greptile P1).
@Kitenite

Copy link
Copy Markdown
Member Author

Addressed the automated review findings in 06ed7cb:

greptile P2 — USER_INTERRUPT_EXIT_CODES missing 129/137 ✅ Fixed. Both sets are now derived from one USER_INTERRUPT_SIGNALS = [1,2,9,15] list (exit codes = 128 + signal), so the signal field and the 128+signal exit-code encoding always classify the same way. Added tests for 129/137 (normal) and 134/139 (still abnormal crash codes).

cubic P3 — Failed test doesn't verify agent identity survives ✅ Fixed. The StopFailure→Failed test now asserts agent: { agentId, sessionId } on the broadcast and agentId/agentSessionId on the store binding, locking in the core property that the binding stays identifiable rather than being dropped.

greptile P1 — markAgentBindingFailed bypasses the in-memory store cache — Verified benign and documented rather than re-plumbed. The only store.get() caller is waitForBinding (terminal-agents.ts), which matches on workspace/agent/definition ids and never reads lastEventType, so the transient staleness has no consumer; the entry is dropped when the dead terminal is disposed. Routing through the store would mean threading it into ~10 createTerminalSessionInternal call sites (a module singleton isn't safe if host-services ever share a process), which is disproportionate for a no-impact latent issue. Corrected the misleading doc comment to state exactly this.

Also: verified end-to-end over CDP (both detectors turn the pane red) — see the updated PR description. host-service typecheck + lint clean; touched test suites green.

…res only

Drop the abnormal-exit heuristic (isAbnormalAgentExit / agent-exit.ts, the
markAgentBindingFailed onExit path, and the failed session-status liveness
plumbing). Detection is now purely hook-reported: Claude Code's StopFailure
maps to the Failed lifecycle event while the session stays alive, so the
binding shows failed via the normal active read — no extra machinery, and no
exit-code false positives.
@Kitenite

Copy link
Copy Markdown
Member Author

Scoped this down to CLI-reported failures only (505a571): removed the abnormal-exit heuristic (isAbnormalAgentExit / agent-exit.ts, the onExit markAgentBindingFailed path, and the failed session-status liveness plumbing). Detection is now purely the StopFailure hook while the session is alive, so the earlier greptile P1 (store-cache divergence) and P2 (exit-code symmetry) are both moot — that code no longer exists. terminal.ts and persistence.ts are back to baseline; the net change is ~110 lines across the event map, UI status, and tests.

This branch was previously deployed

1 inactive deployment
preview — 505a5713 Deployed Jul 14, 2026 by Kitenite via Deploy Web #6531
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.

1 participant