Skip to content

feat(cli): report ambient warnings as a footer badge - #345

Merged
ZouR-Ma merged 1 commit into
mainfrom
feat/cli-warning-badge
Nov 3, 2026
Merged

ZouR-Ma merged 1 commit into
mainfrom
feat/cli-warning-badge

Conversation

@ZouR-Ma

@ZouR-Ma ZouR-Ma commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The Step CLI prints every warning into the transcript as a Warning: ... row. Most of these arrive on their own: startup diagnostics (invalid settings, skill frontmatter), MCP import problems, the tmux keyboard check, model fallback, and warnings raised during a turn. Each one pushes the conversation the user is reading further up the screen, and in a long session it scrolls away before anyone reads it.

Moving all warnings out of the transcript would cause the opposite problem. Many of them answer the user's own input, for example Usage: /cron delete <id>, Full access was not enabled., or the paused-goal reminder. Without an inline row, the command the user just ran would show no visible reply.

Changes

  • The Step footer counts ambient warnings as a badge, ⚠ N, at the end of the bottom line. No badge is drawn when the count is zero. On narrow lines the context readout yields first, so the badge stays visible.
  • A new keybinding app.warnings.open (default alt+i) and a new built-in /warnings command open the session's warning list. The list shows the newest warning first, scrolls with ↑↓, clears with c and closes with Esc. It updates while open and sizes itself to the terminal height.
  • Warnings published before the kernel accepts the user's input stay inline. This covers a command handler and the input hooks before a turn, and acceptance is reported through the existing PromptOptions.preflightResult. Warnings published after acceptance go to the badge.
  • Every user entry point now submits through promptUserInput, which marks that window. The entry points are editor submit, steering during a turn, Alt+Enter follow-ups, the Shift+Tab permission cycle and launch messages.
  • showWarning keeps its inline behavior, because its call sites answer user actions. A new recordWarning is used by the startup sites listed above.
  • At 100 columns and wider the footer names the key that opens the list. On macOS, Terminal.app and iTerm2 often turn Option+letter into a dead key, so alt+<letter> bindings are skipped there and the hint shows /warnings instead.

Behavior and compatibility

  • Only the Step presentation (tuiStyle: "step") changes. The Pi presentation keeps every warning in the transcript. Errors, print/JSON/RPC/SDK modes and session files are unchanged.
  • The new keybinding id app.warnings.open defaults to alt+i, which no existing default uses. alt+w was avoided because it is bound to tui.editor.cursorWordRight. It can be rebound in keybindings.json like any other action, and it is listed in /hotkeys and packages/coding-agent/docs/keybindings.md.
  • The new built-in slash command is /warnings.
  • Warnings are kept in memory per session, capped at 50 entries. They are never persisted and never enter the model's context. Switching sessions or starting a new one shows only that session's warnings.
  • Queued messages that are replayed after compaction finishes are not treated as replies, because the user is no longer waiting on them.
  • docs/step-goal-ui.md describes the badge, the list and the reply rule.

Validation

On macOS (Darwin arm64), with this branch rebased on main (fa10063):

  • pnpm run check: passed.
  • TMPDIR=/tmp ./test.sh from a checkout of this commit under /tmp: passed (packages/coding-agent 5164 passed / 59 skipped, apps/cli 781 passed, all other packages passed).
  • TMPDIR=/tmp ./test.sh from a worktree under ~/Documents: failed in 3 of 3 runs, each time only on packages/coding-agent/test/suite/regressions/7253-manual-compact-during-response.test.ts. An extra threshold compaction ran before the requested manual one. See Limitations.
  • The default macOS TMPDIR additionally fails packages/coding-agent/test/subagent-invocation.test.ts with listen EINVAL, because the tsx IPC socket path exceeds the 104-byte Unix socket limit. TMPDIR=/tmp avoids this and was used for the runs above.
  • Unmodified main (fa10063) passed TMPDIR=/tmp ./test.sh from both /tmp and ~/Documents.
  • Focused apps/cli tests (pnpm exec vitest --run <file>):
    • test/footer-width.test.ts covers badge placement, the key hint, narrow widths and warningListHint.
    • test/interactive-mode-warning-badge.test.ts covers inline versus badge routing, the acceptance window, session scoping, live list updates and the capacity limit.
    • test/interactive-mode-user-input-replies.test.ts covers Shift+Tab, Alt+Enter and launch messages, all submitting through promptUserInput.
    • test/interactive-mode-notify-echo.test.ts and test/suite/regressions/7829-invalid-settings-warning.test.ts were updated for the new routing.
  • packages/coding-agent: test/keybindings.test.ts, test/keybindings-migration.test.ts and test/step-slash-commands.test.ts passed.

Limitations

  • The 7253 failure above is not explained. The test asserts the outcome of a race between threshold and manual compaction (_compactForRequest and _compactBeforeNextAssistantResponse in packages/coding-agent/src/core/agent-session.ts). This change does not touch that code: on the coding-agent side it only adds one keybinding entry and one built-in slash command entry.
  • On this branch the test passed every time it ran outside ./test.sh: 3 of 3 runs alone and 2 of 2 full packages/coding-agent runs. A CPU-loaded full packages/coding-agent run on main did not reproduce it (2 of 2 passed). The failure has only been seen with this branch checked out on one local macOS machine. CI for this PR (build-check-test on ubuntu-latest: pnpm run build, pnpm run check, pnpm test) passed, and the 7253 test ran there as part of pnpm test.
  • The badge and the list are covered by render tests only. They have not been checked by eye in a live terminal.
  • The reply window is tracked with a counter. A background warning that happens to arrive while the kernel is still accepting an input will show inline instead of in the badge.

Startup diagnostics, MCP import problems and warnings raised during a
turn no longer interrupt the Step transcript. The footer counts them as
a warning badge, and alt+i or /warnings opens the session's list (newest
first, c clears, Esc closes). The list is per session and kept in memory
only.

Warnings that answer the user's own input stay inline: anything
published before the kernel accepts the input (a command handler, the
input hooks before a turn) is treated as its reply. Every user entry
point, including Shift+Tab, Alt+Enter and launch messages, now submits
through promptUserInput to mark that window. The Pi presentation keeps
all warnings in the transcript.

Add the app.warnings.open keybinding and the /warnings command. On macOS
the footer hint skips Option+letter bindings, which terminals often
treat as dead keys, and offers /warnings instead.
@ZouR-Ma
ZouR-Ma merged commit 55af3e2 into main Nov 3, 2026
2 checks passed
@ZouR-Ma
ZouR-Ma deleted the feat/cli-warning-badge branch November 3, 2026 06:51
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