Skip to content

fix(slack): drop working pill on codex out-of-credits warning#82

Merged
rogeriochaves merged 1 commit into
mainfrom
codex-out-of-credits-pill
May 31, 2026
Merged

fix(slack): drop working pill on codex out-of-credits warning#82
rogeriochaves merged 1 commit into
mainfrom
codex-out-of-credits-pill

Conversation

@rogeriochaves
Copy link
Copy Markdown
Contributor

Summary

When codex hits its credit ceiling, the rollout emits `task_complete` with `last_agent_message=null` + `has_credits=false`. The bridge already surfaces a clear warning at the channel root:

⚠️ Codex is out of credits (plan: plus, balance 0). The prompt was received but no output was produced, and this will keep failing until credits are topped up at https://chatgpt.com/codex/settings/usage

But then it immediately sets the "is working…" pill on that anchor. The refresh loop re-applies every 30s, so the channel keeps showing a working indicator against a state that's already finished. Operators get a mixed signal — warning above, "Processing…" below.

This PR adds a `terminal?: boolean` flag on `SlackPost` and sets it on the out-of-credits emission. In `bridge.ts`, when a `kind: "text"` post arrives with `terminal: true`, the previous anchor's pill still gets cleared and the warning still posts as the new root — but the `setStatus(WORKING_LABEL)` call is skipped, so nothing claims work is in progress.

Why a flag, not a text match

Coupling the bridge to specific warning text would be fragile (the wording lives in `format.ts`, the pill logic lives in `bridge.ts`, and we'd silently drift). A flag on the post shape makes the intent explicit at the emission site and gives us a clean extension point for any future terminal sentinels (normal task_complete with output, Claude end-of-turn, etc.).

Test plan

  • Updated codex-runtime test asserts the out-of-credits post carries `terminal: true`.
  • `pnpm test` clean (236/236).
  • `tsc` build clean.
  • Once merged + box pulls main, trigger an out-of-credits run on a codex agent and visually confirm the warning posts with no working pill.

When codex returns an out-of-credits sentinel (task_complete with
last_agent_message=null + has_credits=false), the bridge correctly
surfaces the warning at the channel root — but then immediately sets
the WORKING pill on it. The pill refresh loop re-applies every 30s, so
the channel keeps showing "is working…" against a state that's already
finished. Operators see a stuck "Processing…" indicator that contradicts
the warning right above it.

Add a `terminal?: boolean` flag on SlackPost. Set it on the out-of-credits
post in format.ts. In bridge.ts, when a `kind: "text"` post arrives with
`terminal: true`: still clear the previous anchor's pill, still post the
text as the new root, but skip the setStatus(WORKING_LABEL) call — no
work is happening, no pill should advertise that it is.

Could be extended to normal codex task_complete later (which also leaves
a stale pill on the last agent_message anchor), but keeping the scope to
the regression I can see in the screenshot for now. Claude-runtime parity
is separate — Claude has its own end-of-turn signaling.
@rogeriochaves rogeriochaves merged commit 437d1df into main May 31, 2026
1 check passed
@rogeriochaves
Copy link
Copy Markdown
Contributor Author

Extended in 048e59d — same terminal: true mechanism now also fires for Claude transcripts.

The earlier commit only marked codex's out-of-credits sentinel; a screenshot caught the same UX bug on the Claude side after a normal end-of-turn: pill stays lit on the final answer, refresh loop keeps re-applying it, and on the next user message Slack overlays its default localized "Finding answers..." indicator on top.

Changes:

  • formatTranscriptLines / emitAssistantBlocks thread stop_reason through. When it's "end_turn", the LAST text block of that assistant message gets terminal: true (we tag the last text rather than the last block overall — for end_turn responses Claude doesn't emit tool_use, but if that ever changes we want the user-visible final answer to carry the marker, not a buried tool entry).
  • Bridge: terminal text posts now ALSO call setStatus(ts, "") on the new anchor in addition to skipping WORKING_LABEL. Some Slack clients auto-render a default "is thinking" indicator on incoming user messages to assistant-mode apps; an explicit empty setStatus overrides it.
  • Tests added: last text on end_turn carries terminal: true; only the LAST text in a multi-block message is marked (not earlier ones); tool_use stop_reason does NOT mark terminal (work coming); absent stop_reason defaults to working. 240/240.

One acknowledged gap: codex normal completion (task_complete with non-null last_agent_message) has the same UX problem, but the terminal signal arrives in a SEPARATE rollout record from the last text post, so retroactive marking would need a different mechanism. Leaving that for a follow-up — codex out-of-credits is covered and Claude is the more common runtime by a wide margin.

rogeriochaves added a commit that referenced this pull request Jun 1, 2026
…turn)

After PR #82 only the codex out-of-credits sentinel set `terminal: true`,
so the bridge always re-attached the "is working…" pill after every
non-terminal text post. For Claude that meant the pill stayed up forever
after the agent posted its final message and ended the turn — exactly
what you see in the screenshot where the agent's review is posted but
the channel still shows a pill below.

When a transcript-line message carries `stop_reason: "end_turn"` (or
`stop_sequence` / `refusal`), find the last text post we just emitted
from that message and mark it terminal. The bridge already honours that
flag by skipping the pill re-attach. `tool_use` / `max_tokens` /
`pause_turn` stay non-terminal — the agent is still working.

If a turn ends with no text post (e.g. the last block was a tool call),
we leave the pill alone — that's the existing behaviour; rare in
practice, and dropping a pill mid-tool would be more confusing than the
two-minute Slack TTL it relies on otherwise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant