openai / openai/codex-plugin-cc
Session end//clear tears down workspace-shared broker, silently losing other sessions' running jobs
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 33.3k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
Summary
With multiple concurrent Claude Code sessions in one workspace, any session ending (including /clear) tears down the workspace-shared broker while other sessions' background jobs are mid-turn. The in-flight Codex turn is aborted, the detached worker then hangs forever on the dead socket, and once external cleanup reaps the orphan, waiters polling status <job-id> get No job found — the job vanishes without any terminal record. Three background review jobs were lost this way in one afternoon (plugin v1.0.6, macOS, companion state under CLAUDE_PLUGIN_DATA).
Reproduction (timeline from a live incident, timestamps UTC)
Two Claude Code sessions, A and B, same workspace.
- Session A dispatches a background task (
codex-companion task --background), verifies it:statusshowsrunning, worker pid alive,threadIdassigned. A's wait loop (status --wait --poll-interval-ms 30000) polls it healthy for 2 minutes. 19:19:25.015— session B runs/clear./clearfires the plugin's SessionEnd hook (session-lifecycle-hook.mjs~83–114):sendBrokerShutdown(endpoint)→ brokerbroker/shutdown(app-server-broker.mjs~160–164) →appClient.close()→ SIGTERM to the codex app-server (app-server.mjs~259). The rollout file recordsturn_aborted reason "interrupted"at19:19:25.146— 131 ms after the/clear. The hook then deletes broker.json, socket, pid file, and session dir.- Session A's worker process survives (spawned
detached+unref()), but hangs forever on the dead broker socket — it never writes afailedrecord. - External cleanup (a liveness-gated reaper running from a SessionStart hook) correctly identifies broker-dead + idle worker, kills the orphan, and removes the job record.
- Session A's next wait poll returns exit 1,
No job found— from its perspective a verified, running job ceased to exist with no cause recorded.
Jobs whose runtime happened to overlap no session end//clear completed normally — every background job in a multi-session workspace is a race against any session ending during its runtime.
Underlying defects (v1.0.6 line references)
- SessionEnd treats shared state as session-owned. The broker and
state.jsonare per-workspace and serve all sessions, but any single session's SessionEnd shuts the broker down and tears down the state dir without checkingstate.jsonfor active jobs belonging to other sessionIds (session-lifecycle-hook.mjs~100–113). This is the root cause. - Workers do not fail fast on broker death. When the broker socket closes mid-turn, the worker hangs indefinitely instead of erroring so
runTrackedJob's catch (tracked-jobs.mjs~181–202) can write a terminalfailedrecord. The hang means the job's fate is decided by whoever cleans up orphans later, with no trace. cancelinterrupts before persisting.handleCancelcallsinterruptAppServerTurnbefore its first state/log write (codex-companion.mjs~976–987), so a crash between the two (observed: EPERM on the log append in a restricted environment) interrupts the turn while recording nothing.
Suggested fixes
- SessionEnd: before
sendBrokerShutdown, scanstate.jsonforrunning/queuedjobs with a differentsessionId; if any exist, skip broker/state teardown (or refcount attached sessions). LikewisecleanupSessionJobsshould mark its own session's jobsfailed/cancelledrather than deleting their records and files. - Worker: treat broker-socket closure mid-turn as a fatal error so a
failedrecord with a cause is always written. cancel: persist the cancelled state first, best-effort log appends after.
A threadId handle survives in the rollout file even when the job record is erased, which is what made this incident reconstructible — but callers only have that if they captured it before the loss, which is a fragile place to keep the only durable reference.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Trace session-lifecycle-hook.mjs around lines 83–114 and app-server-broker.mjs around 160–164 to understand shared broker teardown, then inspect tracked-jobs.mjs around 181–202 and codex-companion.mjs around 976–987. Reproduce the multi-session /clear timeline and verify that other sessions’ jobs remain represented, broker failure produces a terminal record, and cancel persists state before interruption.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- backend, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100