/goal: chat "stop" message causes silent token burn until usage limit (autonomous tick ignores LLM-level self-restraint)
Summary
When a goal is in Active state and the user sends a chat message instructing the agent to stop (e.g. "STOP", "pause your work", "do not continue"), the LLM enters a reactive self-restraint mode and emits a "Paused. No action taken..." reply on each autonomous continuation turn. However, the goal's ThreadGoalStatus remains Active, so maybe_continue_goal_if_idle_runtime keeps scheduling continuation turns. Each turn invokes the LLM, and the per-turn output (including the "Paused" message) consumes tokens. Token usage climbs steadily until the 5-hour / weekly usage limit is hit, at which point the goal transitions to UsageLimited.
User-visible: the goal appears paused (model says so on every turn) but tokens continue to burn until quota exhaustion.
Repro
- Start a goal:
/goal start <multi-step objective>(any objective that requires >30 minutes of continuations). - After at least one continuation turn fires, send a chat message such as:
STOP. Do not continue this goal until I explicitly resume it. - Observe the model's reply:
Paused. No action taken. The prior STOP still controls; automatic goal continuation is not an explicit restart.(Wording varies per turn — confirming each tick is a fresh LLM call, not a CLI hardcoded line.) - Wait. Subsequent autonomous continuation turns produce the same "Paused" reply repeatedly.
- Eventually:
Goal hit usage limits (/goal resume). Tokens used + elapsed time reflect the goal kept ticking at full frequency the entire interval.
Expected
Any of:
- (a) LLM self-restraint should pair with a runtime back-off — after N consecutive turns producing no tool calls and no objective progress, exponentially extend the continuation poll interval (e.g. 1 min → 10 min → 1 hour) until the next user message.
- (b) The model should be encouraged via system prompt to call
update_goal(status="blocked")when it self-restrains in response to a stop instruction, so the runtime stops scheduling continuations. - (c) Add a
request_pausetool action so the LLM can cooperatively halt the runtime when it chooses to self-restrain, without having to misusecomplete/blocked.
Actual
The autonomous tick (maybe_continue_goal_if_idle_runtime in codex-rs/core/src/goals.rs:1289) only checks ThreadGoalStatus::Active (lines 1311–1319 of the same file) before scheduling another continuation. The LLM has no way to set status to Paused via tool call — update_goal's status enum only accepts complete and blocked (codex-rs/core/src/tools/handlers/goal_spec.rs:65). The user-facing /goal pause TUI command (codex-rs/tui/src/chatwidget/goal_menu.rs) is the only path to set Paused.
Users who instruct the agent via chat (rather than slash commands) therefore have no way to actually pause a goal. The LLM's reactive self-restraint silently burns the full remaining quota.
Why this matters
A common usage pattern: long-running goal, user wants to redirect midway, types wait or stop in chat. They reasonably expect the goal to halt. Instead it appears halted but silently drains quota — sometimes hours of token budget gone with no observable work happening.
The "Paused. No action taken" message itself, repeated over hundreds of turns, is the smoking gun: the wording varies between turns, confirming each is a fresh LLM call producing identical-meaning output, with full per-turn token cost.
Notes
- Tested on Codex CLI built from a recent clone of
openai/codexmain. - Plan: Pro Lite. Both 5-hour and weekly limits affected.
- This is not the
/goal pausecommand path — that one works correctly and does halt the tick.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗