Skip to content

Harden detached launch-failure handling (resume mint-leak + async early-exit surfacing) #159

Description

@davidfarah2003

Surfaced during the mesh review of #23 (session resume). These are pre-existing manager gaps, but session-resume is the first feature to make them both operator-reachable and retry-amplified, so tracking them as a scoped follow-up.

Two related gaps, one root: on a detached cotal start / MCP cotal_spawn, startAgent returns {ok:true} the moment runtime.spawn() hands back a PTY handle — before the child (claude) actually runs.

1. Unsupported-connector resume mints before it fails (early-fixable)

buildLaunch throws for opencode/hermes resume inside startAgent's try — i.e. after provisionAgent mints scoped creds + broker durables (auth mode). So cotal start --agent opencode --resume x fails loud but late, orphaning the minted DM/DLV/role-TASK durables + a creds file for a peer that never launched.

Fix: a declared supportsResume capability on the Connector SPI + a central early preflight check (before the reserve/mint, beside the binary/transcript prechecks). This also makes the default fail-loud for any future connector that neither declares support nor reads opts.resume — closing the fallback-by-omission hole that hermes exemplified (it references opts.resume/opts.mcpServers nowhere; the guard is hand-plugged today). The LaunchOpts.resume doc-comment is the type-level contract; the capability is the runtime-enforcement half TS can't provide.

2. Supported-path async early-exit is neither surfaced nor cleaned up (the dominant leak)

On the claude path a bad id / cwd-mismatch / old-CLI-rejecting---fork-session makes claude exit inside its PTY, and:

  • (i) not surfaced: the caller already saw ✓ started; the peer just never joins the roster. Diagnosable only via cotal attach.
  • (ii) leaks under auth: identity = newIdentity() is a fresh nkey per attempt, and onAgentExit → freeSlot does agents.delete + a cooling stamp only — no deprovision, no creds unlink. The creds file is per-name (bounded/overwritten), but the broker durables are per-identity, so each failed attempt orphans a distinct durable set. Retry-amplified: debugging a typo'd id retries 5–10× → 5–10 orphaned durable sets.

Fix: detect the early child exit before returning ok (or a short presence-wait), and deprovision on early exit (auth-mode scoped — open mesh has no mint, so no leak).

Scope / notes

  • Auth-mode only. Open mesh mints nothing.
  • Manager-internal for part 2 (no core/wire change); part 1 adds one optional Connector capability.
  • Not resume-specific: part 1's late throw is shared by opencode+mcpServers and hermes+win32; part 2's async-exit affects every detached launch. Resume just makes them reachable.
  • Related nice-to-have (from the review): the loadRoster field-picker silently drops an unknown resume: key (can't cause false-resume since it never reaches buildLaunch, but it's the one declarative surface that ignores rather than rejects).

Follow-up to #23 / PR #157.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions