/goal: chat "stop" message causes silent token burn until usage limit (autonomous tick ignores LLM-level self-restraint)

Resolved 💬 5 comments Opened May 26, 2026 by jackzhouusa Closed May 26, 2026

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

  1. Start a goal: /goal start <multi-step objective> (any objective that requires >30 minutes of continuations).
  2. After at least one continuation turn fires, send a chat message such as: STOP. Do not continue this goal until I explicitly resume it.
  3. 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.)
  4. Wait. Subsequent autonomous continuation turns produce the same "Paused" reply repeatedly.
  5. 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_pause tool action so the LLM can cooperatively halt the runtime when it chooses to self-restrain, without having to misuse complete / 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/codex main.
  • Plan: Pro Lite. Both 5-hour and weekly limits affected.
  • This is not the /goal pause command path — that one works correctly and does halt the tick.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗