openai / openai/codex-plugin-cc

runTrackedJob / captureTurn can hang in phase: finalizing indefinitely (no timeout)

Open
#183 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
33.3k
Forks
2.3k
PR merge metrics
No merged PRs in 30d

Description

Summary

A job can be left forever in `status: "running"` + `phase: "finalizing"` after either (a) a successful spark-model turn or (b) a `cancel` that didn't deliver an `interrupted` notification. The job file never transitions to a terminal state, the lockfile persists, and subsequent `--resume-last` against the same workspace is blocked by the zombie.

Plugin version: 1.0.3
Files: `scripts/lib/tracked-jobs.mjs`, `scripts/lib/codex.mjs`

Repro patterns

  • Cancel path: start a long job, `cancel` it. Sometimes the AppServer never emits a `turn/completed (status=interrupted)` or the worker exits before reading it. Job file remains `running/finalizing` forever.
  • Spark path: run a short spark task. Some fraction of turns appear to never emit `final_answer`. `state.finalAnswerSeen` stays false → `scheduleInferredCompletion` doesn't fire → `captureTurn`'s promise stays pending.

Root cause hypothesis

`scripts/lib/tracked-jobs.mjs:142` `runTrackedJob` does:

```js
const execution = await runner(); // <-- no timeout
```

`runner()` resolves only when `captureTurn` (in `scripts/lib/codex.mjs`) sees a terminal AppServer event (`turn/completed`, or an inferred completion via `final_answer`). If neither arrives, the await never resolves and no `catch` runs, so the job file is never written to a terminal status. There is no idle/heartbeat fallback either.

Suggested fix (any of, ideally all)

  1. Hard timeout in `runTrackedJob`: add `taskTimeoutMs` option (default ~15 min, configurable). On expiry, write `failed` + `phase: "timeout"` and reject the runner promise.
  2. Idle watchdog in `captureTurn`: track the last received progress event timestamp; if `now - lastProgress > N` (e.g. 90 s) reject with `no notifications`.
  3. Forced reject after interrupt: `interruptAppServerTurn` should arm a short timer; if the worker hasn't observed an `interrupted` turn within T seconds, reject `captureTurn` with `cancel-not-acknowledged`.

(1) alone is enough to stop the zombies; (2) catches subtler hang modes; (3) makes cancel reliable.

Workaround

None clean. My MCP wrapper can't reach into the companion's job store without coupling to its on-disk format (unstable across upgrades). Operationally I just `--fresh` past the zombie and document manual job-store wipes.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with runTrackedJob in scripts/lib/tracked-jobs.mjs:142 and follow how captureTurn in scripts/lib/codex.mjs resolves terminal events, including cancellation and inferred completion. Reproduce the cancel and spark paths, then verify that a timeout or missing notification produces a terminal job status, releases the lockfile, and no longer blocks --resume-last.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.