Skip to content

feat(init): never clobber a user-defined statusline (#191) - #199

Draft
Frankie-Xu wants to merge 1 commit into
trailhq:mainfrom
Frankie-Xu:feat/191-statusline-optout
Draft

feat(init): never clobber a user-defined statusline (#191)#199
Frankie-Xu wants to merge 1 commit into
trailhq:mainfrom
Frankie-Xu:feat/191-statusline-optout

Conversation

@Frankie-Xu

Copy link
Copy Markdown
Contributor

Summary

  • graft init no longer hides a custom Claude Code statusline. A project-level statusLine that is not Graft's is left untouched (this was already true); --no-statusline / GRAFT_NO_STATUSLINE=1 skip installing one at all, so a bar in ~/.claude/settings.json stays visible.
  • Graft recognises its own bar by the helper path graft-statusline.cjs (not a full-command string match) and will refresh that command on re-init. The choice is recorded in the wiring stamp, so a later session refresh cannot put Graft's bar back.

Closes #191

What was actually happening

Merge already kept a project-level foreign statusLine. The override people hit is:

  1. Custom bar lives in user settings (~/.claude/settings.json).
  2. First graft init writes a project statusLine because .claude/settings.json has none.
  3. Claude Code prefers the project field, so the user-level bar disappears.
  4. Deleting the project field and restarting is not enough: upkeep re-runs init and writes it again.

--no-statusline is the opt-out (same shape as GRAFT_NO_GITIGNORE / --no-global): default install is unchanged for anyone who does not already have a custom bar.

Why not "append as a new line"

Claude Code has one statusLine command per session (the merge warning already said this). Graft's helper already prints multiple lines from that one command (renderStatusline(…).join('\n')). Combining two independent commands would need a wrapper that tees stdin JSON (consumed once) and merges timeouts — too fragile to do by default. To show both, point statusLine at a small wrapper that runs your command then .claude/helpers/graft-statusline.cjs.

Behavior matrix

Existing statusLine First graft init Re-init / wiring refresh
None Writes Graft's helper (unchanged default) Keeps / refreshes Graft's helper
Graft's (graft-statusline.cjs in the command, even an old path) Updates to the current command
Foreign (any other command) Kept, with a warning Kept, with a warning
None, with --no-statusline / GRAFT_NO_STATUSLINE=1 Not written Stamp replays the skip; a prior Graft field is stripped so a user-level bar can show
Foreign, with --no-statusline Kept Kept

Hooks, skill, MCP, and the statusline shim file are still installed. Only the statusLine / subagentStatusLine fields are skipped.

Test plan

  • npm run build && npm test — 891 passing
  • node --import tsx --test test/claude-settings-merge.test.ts test/claude-init.test.ts test/upkeep.test.ts
    • custom statusLine preserved
    • old Graft helper command updated
    • empty settings still get Graft's bar
    • --no-statusline / GRAFT_NO_STATUSLINE=1 skip install
    • stamp replay of --no-statusline
  • Manual: graft init --no-agents --no-statusline in a repo whose ~/.claude/settings.json has a custom bar; project settings have no statusLine; Claude Code still shows the custom bar after restart

Made with Cursor

A foreign Claude Code statusLine is kept, Graft's own helper is refreshed by path, and --no-statusline / GRAFT_NO_STATUSLINE skip installing one so a user-level bar is not hidden.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Frankie-Xu
Frankie-Xu force-pushed the feat/191-statusline-optout branch from 91d8a14 to 178dbb6 Compare August 24, 2026 16:04
@Frankie-Xu

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (ee1ef03). CHANGELOG conflict: kept the 0.13.0 notes and put --no-statusline under Unreleased.

npm run build OK. npm test 924/924.

github-actions Bot added a commit that referenced this pull request Aug 24, 2026
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

🌱 graft blast radius

Nothing outside this diff depends on it. 2 areas changed; no indexed dependents at depth 2.
Tests: no test reaches Wiring Configuration; 1 area updated its tests.

Test signal per changed area — 1 ✓ · 1 ✗

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • Wiring Configuration — 0 of 2 reached · no test file reaches it
    • not reached: wireTarget, rewriteWiring
  • Claude Initialization — 2 of 6 reached · 2 test files changed here: test/claude-init.test.ts, test/claude-settings-merge.test.ts
    • not reached: applyStatusline, envNoStatusline, isGraftStatusline, statuslineWanted

⚠️ 3 changed files not in the graph (.env.example, CHANGELOG.md, README.md) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 11 changed files

Open the interactive graph → — click an area to see its dependent symbols at file:line.

github-actions Bot added a commit that referenced this pull request Aug 25, 2026
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
@Frankie-Xu
Frankie-Xu marked this pull request as draft August 26, 2026 09:53
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.

my custom Statusline was overrided

1 participant