Skip to content

Let one developer use trail just for themselves - #750

Open
anirudhkumar-nanonets wants to merge 2 commits into
mainfrom
claude/trail-just-for-me-d020f2
Open

anirudhkumar-nanonets wants to merge 2 commits into
mainfrom
claude/trail-just-for-me-d020f2

Conversation

@anirudhkumar-nanonets

Copy link
Copy Markdown
Collaborator

The issue: A developer who wanted trail only for themselves couldn't tell whether to commit what trail init added, and committing it would change the repo for everyone, so they uninstalled it.
Why: trail init always set trail up for the whole team. It left .trail/ and .claude/settings.json for git to pick up, ended with "commit … once", and later sessions kept asking to commit learnings and to share them.
The fix: trail init now asks who trail is for. Just me, which enter picks, keeps trail's files out of git for that clone only and saves every learning in ~/.trail, so git status stays clean and nothing asks to commit or share. trail init --team shares it later.

Moves: week-one retention after trail init, the people whose first trail init was 8 to 14 days ago who opened a Claude session in a trail repo (repo_seen) in the last 7 days. PostHog events in Metabase db 11 (posthog_event_full_view). Today, 2 Nov 2026: 382 of 862 (44%). This PR adds scope (just_me / team / unasked) to init_completed so the number can be split by answer; that split is none today, since no release sends it yet.

What changes

  • trail init asks one question after the agent picker, on a terminal, in a git repo whose .trail/ isn't committed yet: just me (enter) or my team. --just-me and --team answer it without asking. An agent's run, --yes, --pair and --connect don't ask and stay as they were (team). A repo already on just me stays so until --team.
  • Just me is a mark in the repo's folder in ~/.trail (src/notes/just-me.ts), so every clone and worktree reads the same answer. With it:
    • .trail/ goes in the clone's own .git/info/exclude, along with any new file init writes for another agent. A file git already tracks can't be hidden, so init names it.
    • .claude/settings.json gets nothing, and an entry an earlier init put there comes back out (a file git tracks is left alone). The hooks in ~/.claude run trail.
    • writeNote saves a learning for the repo in ~/.trail instead of .trail/learnings/, so trail note and init's look-back both keep it on the machine.
    • No sign-in link to share (cloudLines), no "commit .trail/ once" line at session start (uncommittedDirective tells Claude there is nothing to commit instead), and init's closing line says there's nothing to commit and how to share later.
  • trail init --team takes the mark off, lets git see .trail/ again and writes the shared session-start entry, and init ends with the usual commit line. Learnings saved while on just me stay personal.

Why hide the files instead of moving them out

Trail finds its map, its hook and its status line in the repo's .trail/, and mapDirFor and repoWiredForTrail key off that folder, so moving them out of the repo would touch every reader. .git/info/exclude is per clone and never committed, so it gets the same result for git with no change to those paths. Learnings are the one thing that should outlive a fresh clone, so only they move to ~/.trail.

Checked

  • test/trail-just-me.test.ts (5 tests): learnings land in ~/.trail while the mark is on and in .trail/ after; no share link and the just-me session-start line; the Claude Code wiring leaves .claude/settings.json alone and takes an earlier entry back out; .trail/ hidden and shown again; trail init --just-me leaves git status empty, then --team brings back .claude/ and .trail/ and the commit line.
  • A real run in a fresh clone of expressjs/cors: trail init with enter at both questions, then git status reads "nothing to commit, working tree clean"; trail note without --personal saves to ~/.trail and the tree stays clean; trail init --team then shows .claude/ and .trail/ to commit.
  • Full suite: 4,053 of 4,149 passed, on a laptop running three other suites at once. The 37 files with failures, rerun side by side with main, failed 30 tests here and 31 on main, and the ones that differed (hook timing and FIFO tests) pass on their own on both.
  • trail check flags 2 conflicts, both on the new trailHome import in src/cli.ts, against learnings about testing hooks on a sandbox HOME. They match on shared words (home, claude, trail), and this change doesn't run Claude or touch how HOME is set.

Merge order

This PR stays out of the lines #732, #733, #738, #739 and #731 change (test merges onto this branch are clean). Two touch the same telemetry lines:

🤖 Generated with Claude Code

@trailhq-graft

trailhq-graft Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

5 areas changed → 9 areas can be affected. 36 dependent symbols, depth 2.
Tests: no test reaches Init Command; 3 areas updated their tests.
Tag: @shhdwi — 14 of 14 areas · Rushabh Nagda — Cloud Nudge · @afeddersen — Telemetry Tracking

flowchart TB
  A0(("Prompt Execution Engine<br/>13 symbols"))
  A1(("Keep Guardrail<br/>7 symbols"))
  A2(("Memory And Review Notes<br/>6 symbols"))
  A3(("Session Learning<br/>3 symbols"))
  A4(("Cloud Activity Pairing<br/>2 symbols"))
  AX(("4 smaller areas<br/>5 symbols"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2,A3,A4 reached;
  classDef tail fill:#EEF2F3,stroke:#9AA4A9,stroke-width:1px,color:#3A4247;
  class AX tail;
Loading
Can be affected Symbols Nearest hop Reached from
Prompt Execution Engine 13 src/claude/session-metrics.ts:L1-L215 session-metrics.ts — imports, depth 1 Cloud Nudge, Telemetry Contract
Keep Guardrail 7 src/init/keep.ts:L121-L133 keepGuardrail — calls, depth 1 Note Privacy, Telemetry Contract
Memory And Review Notes 6 src/notes/memory-notes.ts:L230-L244 saveMemoryCandidates — calls, depth 1 Note Privacy, Telemetry Contract
Session Learning 3 src/learn/sessions.ts:L265-L273 saveSessionLearnings — calls, depth 1 Note Privacy, Telemetry Contract
Cloud Activity Pairing 2 src/cloud/lookup-activity.ts:L1-L60 lookup-activity.ts — imports, depth 2 Telemetry Contract
Telemetry Tracking 2 src/telemetry/sessions.ts:L1-L113 sessions.ts — imports, depth 1 Telemetry Contract
3 smaller areas 3 CLI Skills, Wiring Rewrite, MCP Tools see below
Who knows this code — 4 people across 14 areas
Area Who knows it
Telemetry Contract · changed @shhdwi — 1 commit, last 10d ago
Note Privacy · changed @shhdwi — 3 commits, last 7d ago
Git Trail Initialization · changed @shhdwi — 17 commits, last 6d ago
Cloud Nudge · changed @shhdwi — 4 commits, last 6d ago · Rushabh Nagda — 1 commit, last 12d ago
Init Command · changed @shhdwi — 51 commits, last 6d ago
Prompt Execution Engine · affected @shhdwi — 25 commits, last 6d ago
Keep Guardrail · affected @shhdwi — 10 commits, last 6d ago
Memory And Review Notes · affected @shhdwi — 4 commits, last 6d ago
…6 further areas

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

All 36 dependent symbols, grouped by area

Prompt Execution Engine — 13 symbols in 12 files

  • src/claude/session-metrics.ts:L1-L215 — session-metrics.ts (imports, depth 1)
    67: * "graft" (or a repo working on graft itself) must not count as a graft read —
  • src/claude/skills-wiring.ts:L1-L116 — skills-wiring.ts (imports, depth 1)
    16: import { hookMsBucket } from '../telemetry/contract.js';
  • src/claude/state.ts:L1-L165 — state.ts (imports, depth 1)
    33: /** The repo skills this session used, by name, to when it last went to the workspace as a run (skill-use.ts). */
  • src/claude/format.ts:L1-L416 — format.ts (imports, depth 2)
    37: return [C.muted(`◤ ${brand()} · not built · run `) + C.text(cmd('graft build'))];
  • src/claude/hook-io.ts:L1-L140 — hook-io.ts (imports, depth 2)
    17: /** The env a hook's graft child runs with. In a repo on trail with no map yet, the
  • src/claude/prompt-hook.ts:L259-L297 — handlePrompt (calls, depth 2)
    263: // `/name` for one of the repo's skills is a run of it; the name only goes.
  • src/claude/prompt-hook.ts:L1-L307 — prompt-hook.ts (imports, depth 2)
    41: * and never wait on the ask; the ask gets only what is left. A large repo's
  • src/claude/prompt-skills.ts:L1-L327 — prompt-skills.ts (imports, depth 2)
    26: import { lastGoodRun } from '../trace/run-verdict.js';
  • src/claude/skill-use.ts:L1-L80 — skill-use.ts (imports, depth 2)
    2: * A coding agent using one of the repo's skills, as a run on the workspace.
  • src/claude/stats.ts:L1-L27 — stats.ts (imports, depth 2)
  • src/claude/statusline.ts:L1-L39 — statusline.ts (imports, depth 2)
  • src/claude/stop.ts:L1-L216 — stop.ts (imports, depth 2)
    47: * (Cursor and Copilot CLI run these hooks with transcripts of their own), or
  • src/claude/sync-run.ts:L1-L48 — sync-run.ts (imports, depth 2)
    17: // unconfigured repo's rebuild sees byte-identical argv to before this existed.

Keep Guardrail — 7 symbols in 3 files

  • src/init/keep.ts:L121-L133 — keepGuardrail (calls, depth 1)
    126: if (input.applies === 'every repo') {
  • src/init/keep.ts:L135-L150 — keepPreference (calls, depth 1)
    142: if (item.scope !== 'repo' && item.applies === 'every repo') {
  • src/init/keep.ts:L1-L387 — keep.ts (imports, depth 1)
    9: * - A guardrail: the catalog's template (`guardrailLearning`), every repo
  • src/init/run.ts:L1-L445 — run.ts (imports, depth 1)
    3: * no-model look-back in a worker while init wires the repo, then show what
  • src/init/keep.ts:L184-L252 — keepItems (calls, depth 2)
    186: const at = place ?? { dir: process.cwd(), repoKey: all[0]?.applies && all[0].applies !== 'every repo' ? all[0].applies : '' };
  • src/init/keep.ts:L172-L177 — keepReview (calls, depth 2)
  • src/init/review-screen.ts:L1-L316 — review-screen.ts (imports, depth 2)
    44: found: '✓ looked back at {n} Claude Code sessions about this repo, typed into on {d} days ({from} to {to})',

Memory And Review Notes — 6 symbols in 2 files

  • src/notes/memory-notes.ts:L230-L244 — saveMemoryCandidates (calls, depth 1)
    230: export function saveMemoryCandidates(repo: string, kept: MemoryCandidate[], author = noteAuthor(repo), date = new Date().toISOString().slice(0, 10)): Note[] {
  • src/notes/memory-notes.ts:L1-L280 — memory-notes.ts (imports, depth 1)
    2: * Learnings from the notes Claude Code keeps for this repo on this machine
  • src/notes/pr-reviews.ts:L525-L541 — saveReviewFindings (calls, depth 1)
    525: export function saveReviewFindings(repo: string, findings: ReviewFinding[], author = noteAuthor(repo), date = new Date().toISOString().slice(0, 10)): Note[] {
  • src/notes/pr-reviews.ts:L1-L577 — pr-reviews.ts (imports, depth 1)
    2: * Learnings from a repo's pull requests, for `trail init` to offer on day one
  • src/notes/memory-notes.ts:L252-L279 — finishMemoryRead (calls, depth 2)
    253: repo: string,
  • src/notes/pr-reviews.ts:L549-L576 — finishReviewRead (calls, depth 2)
    550: repo: string,

Session Learning — 3 symbols in 1 file

  • src/learn/sessions.ts:L265-L273 — saveSessionLearnings (calls, depth 1)
    265: export function saveSessionLearnings(repo: string, findings: Array<Learning & { touches?: string[] }>, author = noteAuthor(repo), date = new Date().toISOString().slice(0, 10)): Note[] {
  • src/learn/sessions.ts:L1-L310 — sessions.ts (imports, depth 1)
    2: * Learnings from a repo's past Claude Code sessions, for `trail init`: what
  • src/learn/sessions.ts:L281-L309 — finishSessionRead (calls, depth 2)
    282: repo: string,

Cloud Activity Pairing — 2 symbols in 2 files

  • src/cloud/lookup-activity.ts:L1-L60 — lookup-activity.ts (imports, depth 2)
    8: import { noteAuthor } from "../notes/git-facts.js";
  • src/cloud/pair.ts:L1-L368 — pair.ts (imports, depth 2)
    12: * review, and uploads the repo the usual way, which leaves a dropped learning

Telemetry Tracking — 2 symbols in 2 files

  • src/telemetry/sessions.ts:L1-L113 — sessions.ts (imports, depth 1)
    20: * (the force-close) — both places guaranteed to run at the right moment. A hook
  • src/telemetry/track.ts:L1-L162 — track.ts (imports, depth 1)
    41: repo?: string;

CLI Skills — 1 symbol in 1 file

  • src/skills/cli-skills.ts:L1-L376 — cli-skills.ts (imports, depth 2)
    5: * person's: run by an agent (`personAtTerminal` says no), they print who may

Wiring Rewrite — 1 symbol in 1 file

  • src/upkeep-run.ts:L45-L65 — rewriteWiring (calls, depth 2)
    45: function rewriteWiring(repo: string, hosts: string[], opts: WiringOpts, toTrail = false): void {

MCP Tools — 1 symbol in 1 file

  • src/mcp/tools.ts:L1-L337 — tools.ts (imports, depth 2)
    39: const noGraph = (): string => `no graph found — run \`${cmd('graft build')}\` first`;
Test signal per changed area — 3 ✓ · 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.

  • – Telemetry Contract — 8 test files reach it, none changed here
  • ✓ Note Privacy — 3 of 3 reached · 1 test file changed here: test/trail-just-me.test.ts
  • ✓ Git Trail Initialization — 2 of 6 reached · 1 test file changed here: test/trail-just-me.test.ts
    • not reached: runTrailInit, dropTrailShared, gitTracks, withoutOurs
  • ✓ Cloud Nudge — 1 of 1 reached · 1 test file changed here: test/trail-just-me.test.ts
  • ✗ Init Command — 0 of 3 reached · no test file reaches it
    • not reached: runInitCommand, askJustMe, trailCommitted
75 test suites also reference this code

86 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/claude-copy-dashes.test.ts
  • test/claude-format.test.ts
  • test/claude-hook-timeouts.test.ts
  • test/claude-hooks-1b.test.ts
  • test/claude-hooks-perf.test.ts
  • test/claude-hooks-skills.test.ts
  • test/claude-hooks.test.ts
  • test/claude-host-standdown.test.ts
  • test/claude-init.test.ts
  • test/claude-post-bash-walls.test.ts
  • test/claude-pre-bash-wiring.test.ts
  • test/claude-prompt-learnings.test.ts
  • test/claude-prompt-private.test.ts
  • test/claude-session-start-learnings.test.ts
  • test/claude-settings-merge.test.ts
  • test/claude-shim-template.test.ts
  • test/claude-skills-journey-fixtures.ts
  • test/claude-start-skills-1b.test.ts
  • test/claude-state.test.ts
  • test/claude-statusline.test.ts
  • …55 more

⚠️ 3 changed files not in the graph (.trail/learnings/2026-11-02-trail-just-for-one-person-hides-anirudhkumar.md, README.md, TELEMETRY.md) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 12 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 Nov 2, 2026

This branch has not been deployed

No deployments
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