Skip to content

fix(hooks): tell the user when a save can't add a widget's hooks - #703

Merged
sirmalloc merged 3 commits into
sirmalloc:mainfrom
eric-engberg:fix/hook-wrapper-notice
Oct 26, 2026
Merged

sirmalloc merged 3 commits into
sirmalloc:mainfrom
eric-engberg:fix/hook-wrapper-notice

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

Relates to #394: this covers its wrapper-script case. The issue's original cause isn't known, so this doesn't close it.

What

  • The notice: when a save can't add the Claude Code hooks a widget needs (today only Skills has hooks), Ctrl+S and Save & Exit show a notice instead of "✓ Configuration saved". It names:
    • the widgets that lost their hooks;
    • the status line command;
    • how a wrapper script gets the hooks.
  • Continue: returns to the screen Ctrl+S was pressed on, or exits after Save & Exit.
  • Docs: a "Skills always shows none" note in docs/USAGE.md, with a pointer from the README next to the other status line notes.

Why

Skills reads skill activity that Claude Code hooks record. Since upstream #674, saving adds those hooks only when the status line command contains ccstatusline, because any other command may be another tool's script, and Claude Code adds UserPromptSubmit output to the prompt. That rule stays.

Its cost is a wrapper such as bash ~/.claude/status.sh that runs ccstatusline inside. It gets no hooks:

  • the save still says "✓ Configuration saved";
  • Skills shows none for good;
  • nothing tells the user why, which is what Skill is always "none" #394 reports.

The fix for such a wrapper is easy once you know it. I checked it with the built CLI under Bun and Node:

  • a script named ~/.claude/ccstatusline-wrapper.sh running ccstatusline "$@";
  • run as bash ~/.claude/ccstatusline-wrapper.sh --hook with a UserPromptSubmit payload for /commit;
  • it logged the skill.

How

  • syncWidgetHooks returns the hooks it left out, { statusCommand, widgetNames }, or null. It returns null when:

    • every needed hook was added;
    • no widget needs a hook;
    • no status line is installed yet (installing ccstatusline adds the hooks, as before).

    The rule itself is unchanged and stays in hooks.ts.

  • saveSettings passes that result on. A failed hook sync still counts as a successful save, and returns null.

  • App.tsx:

    • Both save paths show the existing FlowNotice (as Install Complete does) when hooks were left out.
    • A notice's continueScreen can be 'exit', so Save & Exit's notice closes the TUI on Continue.
    • Ctrl+S pressed on a notice returns to the main menu.
    • FlowNotice's props leave out continueScreen, which only App reads, in onContinue.

Demo

With Claude Code's status line set to bash ~/.claude/statusline.sh, saving a line that has Skills shows the notice, and Continue returns to the main menu.

Powerline

Saving with Skills behind a wrapper command shows the hooks notice, Powerline mode

Plain

Saving with Skills behind a wrapper command shows the hooks notice, plain mode

Testing

  • hooks.test.ts:
    • The other-tool case returns { statusCommand, widgetNames: ['Skills'] }.
    • A shell-wrapped ccstatusline command returns null.
    • Two new cases: no widget needs hooks, and no status line installed.
  • config.test.ts: a new case checks that saveSettings returns the left-out hooks.
  • App.test.ts (Ink, waiting for each key's effect):
    • Ctrl+S on Edit Lines shows the notice, and Continue returns to Edit Lines.
    • Save & Exit saves, shows the notice, and Continue exits.
    • Both passed 12 runs in a row.
  • Full suite and lint: bun test 3117 pass, 0 fail; bun run lint passes.
  • Node (Vitest): the three changed test files pass 64 of 64.
  • Built CLI under Bun 1.4.2 and Node 26.10.0:
    • Piped renders agree across runtimes and match main in plain and Powerline modes.
    • The TUI opens and exits cleanly.

Skills reads skill activity that Claude Code hooks record. Since hooks
are only added when the status line command contains `ccstatusline`, a
status line run through a wrapper such as `bash ~/.claude/status.sh`
gets none, and saving says "✓ Configuration saved" while Skills keeps
showing "none" with nothing to explain why.

syncWidgetHooks now returns the hooks it left out: the status line
command and the names of the widgets that needed them. It reports
nothing when every hook was added, when no widget needs one, or when no
status line is installed yet (installing ccstatusline adds them).
saveSettings passes that on.

When a save leaves hooks out, Ctrl+S and Save & Exit show a notice
instead of the usual confirmation: which widgets lost their hooks, the
status line command, and how a wrapper script gets them (put
"ccstatusline" in its name and pass its arguments on). Continue returns
to the screen Ctrl+S was pressed on, or exits after Save & Exit.
Add a troubleshooting note under Claude Code Status Line Settings, and a
README pointer next to the other status line notes: Skills needs hooks,
which are only added when the status line command contains
`ccstatusline`, and how a wrapper script gets them.
FlowNotice took every field of FlowNoticeState, continueScreen
included, but never read it: App decides where Continue leads, in the
onContinue it passes. Now that continueScreen can also be 'exit',
which only App can act on, leave it out of the notice's props.
@sirmalloc
sirmalloc merged commit f6e2369 into sirmalloc:main Oct 26, 2026
3 checks passed
axisrow added a commit to axisrow/ccstatusline that referenced this pull request Oct 27, 2026
…it names, sirmalloc#717 claude-settings test isolation, sirmalloc#705 jj detached HEAD, sirmalloc#703 hooks save warning, sirmalloc#700 dev reload, sirmalloc#698 renderer merge ends at hidden widget, sirmalloc#697 block reset timer under-limit hide, sirmalloc#745 dev-deps bumps

Conflict policy: upstream infra wins, fork features ported on top (fork-only
src/daemon/, src/widgets/editors/*, parity fixtures and fork widgets kept).
Notable joins: sirmalloc#703 returns SkippedWidgetHooks through the fork's
saveSettingsTo wrapper; sirmalloc#697 keeps the fork's zero hide state alongside
upstream's under-limit state; sirmalloc#698 replaces the fork's merge-glue palette
semantics with upstream's merge-ends-at-unmerged-empty-widget rule;
dev-reload (ctrl+r) composes with the fork's first-run preset picker.

Co-Authored-By: Claude Code <[email protected]>
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Oct 29, 2026
Upstream: Extra Usage Limit widget (sirmalloc#756), live widget preview while picking (sirmalloc#755), Fast Mode widget (sirmalloc#754), transcript block fallback fixes (sirmalloc#752), editor keys on a global install (sirmalloc#751), git cache TTL (sirmalloc#750), npx cached copy skip (sirmalloc#749), Block Usage hide at 100% (sirmalloc#747), Block Reset Timer hide until 100% (sirmalloc#697), fish-style branch abbreviation (sirmalloc#614), jj detached HEAD (sirmalloc#705), hooks save warning (sirmalloc#703), dev reload (sirmalloc#700), merge ends at a hidden widget (sirmalloc#698), Block Forecast and Block Limit Timer widgets (sirmalloc#699), contributor guide (sirmalloc#706), POSIX path case fix (sirmalloc#707), whitespace-distinct conflict filenames (sirmalloc#709), Quick Start reorder (sirmalloc#715), bunx launch guidance (sirmalloc#714), jj diff summary counts (sirmalloc#711), powerline manual separators (sirmalloc#713), keychain credential by account (sirmalloc#532), Keychain probe timeout (sirmalloc#760), usage cache scoped per CLAUDE_CONFIG_DIR (sirmalloc#266), test isolation (sirmalloc#717, sirmalloc#753), dev-dependency bumps (sirmalloc#745)

Conflicts resolved per the standard-resolution table; fork deletions re-applied.

Hand edits outside conflicts:
- src/utils/global-command-resolution.ts: fork-rename (ccstatusline@ -> ccstatusline-usage@ in upstream's new bunx pattern)
- src/utils/__tests__/global-package-manager.test.ts: fork-rename (package dir ccstatusline-usage in upstream's new tests)
- src/utils/usage-fetch.ts: compat fix - cacheUsageData gained a fetchedAtMs arg (upstream sirmalloc#266); adapted the fork-only fetchFreshUsageData call site
- src/utils/__tests__/usage-fetch.test.ts: compat fixes - cache/lock now hashed per CLAUDE_CONFIG_DIR (usageFilePath helper) in four fork tests; upstream fableUsage renamed to fork's weeklyFableUsage; seedUsageCache takes TokenHome
- src/utils/__tests__/usage-prefetch.test.ts: compat fix - mockFetchUsageData renamed to upstream's mockFetchUsage in cleanly-merged tests
- README.md: took upstream's Quick Start move (sirmalloc#715) and dropped the fork's old copy; fork-rename of its ccstatusline@ specifiers; links to fork-deleted docs (docs/USAGE.md, docs/WINDOWS.md) replaced with the fork's inline text
- CONTRIBUTING.md: links to the fork-deleted docs/DEVELOPMENT.md pointed at the README Development section
- docs/test-retirements.md: ledger entries for the 12 upstream test renames (sirmalloc#697, sirmalloc#698, sirmalloc#700, sirmalloc#705, sirmalloc#713, sirmalloc#760)
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.

2 participants