Skip to content

Background jobs: prompt text swallowed as options (-m pytest -> model 404); dead workers never finalized; cancel hangs; stderr discarded #699

Description

@4sj9wrhgbp

Summary

Four defects found while diagnosing reliability issues with background jobs on Windows. All verified against plugin source v1.0.6 with session logs and on-disk job state. Environment: Windows 11, plugin 1.0.6, codex CLI 0.148.0 / 0.150.1.

1. Natural-language prompt words are swallowed as CLI options (-m pytest → model pytest)

When a caller passes a task as a single raw argument string, splitRawArgumentString (scripts/lib/args.mjs:76-127) tokenizes it shell-style, and parseArgs then interprets -m via aliasMap: { m: "model" } (codex-companion.mjs:764-768). A prompt containing python -m pytest tests parses to options.model = "pytest", which normalizeRequestedModel (codex-companion.mjs:103-111) passes through unchanged, producing gateway 404s:

Model "pytest" is not supported

Reproduced 3× (rollout turn_context with model="pytest" at 3 timestamps; corresponding job logs show the 404s). This is version-independent (parsing happens in the plugin).

Suggested fix: don't shell-split structured input; pass prompts as a dedicated field or require -- separation; drop or rename the m alias; validate model values against the known catalog before sending.

Workaround: avoid bare -m in prompt text, or use --prompt-file.

2. Dead background workers are never finalized (zombie running jobs)

Background jobs spawn detached workers with stdio: "ignore" + child.unref() (codex-companion.mjs:671-682), and job state is written after spawn (684-698). If the worker dies at any point, nothing marks the job failed — tracked-jobs.mjs:142-203 only finalizes on normal runner return/throw, and there is no watchdog, PID liveness check, or timeout reclaim (job-control.mjs:281-307 treats queued/running as active indefinitely).

On-disk evidence: jobs created days apart still running with dead PIDs; one from Aug 21 whose log contains only Starting Codex Resume.. Two jobs from different workspaces/brokers stopped at the same second (network-level kill event) and both remain running forever.

Suggested fix: worker heartbeat or PID liveness checks; queued/running timeout reclaim; write a terminal state whenever the worker process is observed dead.

3. cancel hangs because it awaits an interrupt with no timeout

handleCancel awaits interruptAppServerTurn before killing the process tree (codex-companion.mjs:963-987), with no timeout wrapper (codex.mjs:960-992). If the broker/turn is already gone, the await blocks indefinitely; terminateProcessTree and the status write never execute. Corroborating evidence: the entire job-state directory contains zero cancelled records despite explicit user cancels. Minor secondary issue: terminateProcessTree(job.pid ?? NaN) when pid is absent.

Suggested fix: short timeout on interrupt → force-kill process tree on expiry → always write a terminal state; skip the interrupt path entirely for jobs whose PID is already dead.

4. All worker stderr is discarded (zero observability)

stdio: "ignore" means codex CLI / app-server stderr (connection drops, retry storms, panics) is never persisted. This made root-causing #2 impossible from local evidence — the actual death cause of the same-second worker deaths could not be determined because all evidence was destroyed by design.

Suggested fix: persist worker stdout/stderr to the job log (even best-effort tail), at least around spawn/connect/turn lifecycle transitions.

Workarounds in use

Foreground mode for critical tasks; avoid bare -m in prompts; periodic cleanup of zombie job files and orphaned cxc-* temp dirs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions