Skip to content

Agent runtime: process-ownership fix (#3308) covers Claude chat and headless only — Codex/Gemini keep a stale registry entry, and a failure before the reader starts leaks one #3408

Open

Description

@vybe

Follow-up to #3308 (fixes #3307), found while validating it for the merge train. Neither blocks that PR.

1. The fix covers two of four runtimes

#3308 makes process_registry.unregister() refuse to drop an entry whose process is still alive, and adds worker-completion cleanup so the executor thread unregisters once the child exits. The registry change is global; the worker cleanup was added only to Claude chat and headless.

codex_runtime.py and gemini_runtime.py still rely on the waiter's finally. When the waiter is cancelled while the child is alive, that finally is now a no-op, and nothing unregisters after the child exits: the entry stays, and log subscribers never receive stream_end.

Found by reading the code; no test covers it.

2. A failure between register() and the reader start leaks the entry

Paths that call registry.unregister() after a failed stdin write (claude_code.py, gemini_runtime.py in two places, headless_executor.py) are now a no-op when the child is still alive, and nothing retries.

Reproduced with a probe (live child, stdin write forced to raise): on the #3308 head the child stays registered, listed as running and allowlisted from the orphan sweep, and the entry and its log buffer remain after the child dies. On dev before #3308 the entry is dropped.

Reachability is unproven: in the realistic broken-pipe case the child is already dead or dying.

Acceptance criteria

  • Codex and Gemini runtimes unregister on worker completion after an abandoned waiter, with a test per runtime that executes the path (cancel the waiter with a live child, let the child exit, assert the entry is gone and stream_end is delivered).
  • The failed-write paths terminate the process group before unregistering, with a test that drives a live child through a failing stdin write and asserts no entry and no live child remain.
  • Decide and document whether an abandoned child should still be eligible for the periodic orphan sweep (after fix: retain subprocess ownership after waiter cancellation #3308 it runs to its own deadline with the result discarded).
  • termination_unconfirmed is named in the error detail the agent router returns instead of a generic 500.

Activity

  1. added
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    complexity-mediumComplexity: medium (board points 5-8)
    on Oct 8, 2026
  2. AndriiPasternak31 commented on Oct 27, 2026

    @AndriiPasternak31
    Contributor

    /claim

  3. added and removed
    status-readyGreenlit and ready for development (vetted; counterpart to status-incubating)
    on Oct 27, 2026
  4. github-actions commented on Oct 27, 2026

    @github-actions

    Claimed by @AndriiPasternak31. Label status-in-progress added.

    Next steps:

    1. Create branch: feature/3408-<short-slug>
    2. Implement the feature
    3. Open a PR with Fixes #3408
  5. added
    status-in-devMerged to dev, awaiting release cut to main
    and removed on Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions