Skip to content

Make trail work in Claude Desktop and Codex - #733

Merged
shhdwi merged 5 commits into
mainfrom
claude/desktop-and-codex-hosts-cde008
Nov 8, 2026
Merged

shhdwi merged 5 commits into
mainfrom
claude/desktop-and-codex-hosts-cde008

Conversation

@anirudhkumar-nanonets

@anirudhkumar-nanonets anirudhkumar-nanonets commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

The issue: Until its hooks were trusted in /hooks, Codex never heard how to save a learning. Asked to save one, it edited a teammate's learning file by hand. In Claude Desktop worktree sessions, editing a file never brought its learnings, and neither app was counted as its own host.
Why: AGENTS.md only covered code search, and trail note wrote to ~/.trail (which Codex's sandbox can't write) before the repo. Desktop's hooks get the main checkout as the project, so a worktree file's path never matched a learning. agent_host had no value for either app, and hook events from Codex said cli.
The fix: AGENTS.md gets a Learnings part. A repo learning saves even when ~/.trail can't be written. Touch learnings take the file's path in its own checkout. A hook puts all its events, and the trail commands it starts, down to the payload's host, now including codex and claude-desktop.

Moves: active users by host, PostHog Postgres (Metabase db 11, posthog_event_full_view), distinct installs over the last 7 days with a repo_seen, query, note_saved or learnings_committed event, cli_name = 'trail', by agent_host. On 2026-10-29: claude-code 1,157 · cli 677 · mcp 262 · claude-desktop and codex 0 (the values don't exist before this PR).

What works where (checked with real sessions on 2026-10-30)

Host Learnings at start Saving one Reusing one agent_host
Claude Desktop Code tab (worktree session) works (from the main checkout's branch) works partly → works claude-code → claude-desktop
Codex, hooks trusted works no → partly (saves when asked; never unasked) works cli → codex
Codex, hooks not trusted (right after trail init) no → partly (AGENTS.md) no → partly (same) works (trail ask) none
Claude Desktop chat (Trail connector, trail-cloud) partly partly (saves to the workspace, not the repo) works not in this repo

Codex: five real sessions

Codex 0.162 (the CLI inside ChatGPT.app, which the app runs as its app-server), default model, workspace-write sandbox, each run in its own home outside /tmp so the sandbox can't write it. "Before" is trail 0.26.0 as installed; "after" is this branch. The repo had one committed learning (money stays in integer cents). Prompt: add applyDiscount with a test; "half-cents always round down".

Run Reached the model about learnings Used the learning Saved the rule unasked Then asked to save
A before, untrusted nothing yes, via trail ask no edited the teammate's learning file by hand
B after, untrusted AGENTS.md Learnings part yes, via trail ask no trail note: turned away for naming no file, then saved inside the sandbox
C after, trusted (--dangerously-bypass-hook-trust) session-start text, prompt learnings, save directive, AGENTS.md yes, credited no not asked
D before, trusted session-start text, prompt learnings, save directive yes, credited no not asked
E after, trusted every queued event from the session says codex (hook_ms ×3, prompt-hook ask, stop-hook check, repo_seen, learnings_committed); in D they said cli

Codex's shell sets CODEX_THREAD_ID, CODEX_SESSION_ID, CODEX_SANDBOX (seen), so detectHost reads those. But a trail command Codex runs itself can't write the event queue under ~/.graft, so those send nothing either way.

Claude Desktop: this session

The transcript and trail's state files show SessionStart, UserPromptSubmit, PostToolUse, PostToolUseFailure, PreToolUse on Bash and Stop all ran; the trail skill and MCP server loaded. PreToolUse on Edit ran but never found a learning: in a worktree session CLAUDE_PROJECT_DIR is the main checkout, so learningsOnTouch saw .claude/worktrees/<name>/src/... (same cause #738 fixed for overlap warnings).

Changes

  • src/hosts/instructions.ts: under trail, the AGENTS.md block (also GEMINI.md, Copilot) ends with ### Learnings: where they are, read them via trail ask, save with trail note, commit them, run trail check. Graft's text is unchanged.
  • src/notes/notes.ts: writeNote falls back to repoPlace when ensureRepoHome throws and the learning goes in the repo. --personal still needs ~/.trail.
  • src/claude/hooks.ts: learningsOnTouch takes the file's path relative to checkoutRoot(dirname(file)); learnings and session state still come from the project dir, so nothing is shown twice. The hook sets TRAIL_AGENT_HOST from the payload's host.
  • src/telemetry/track.ts, contract.ts, TELEMETRY.md: agent_host adds claude-desktop (CLAUDE_CODE_ENTRYPOINT === 'claude-desktop') and codex (Codex's shell variables, or TRAIL_AGENT_HOST from a hook). Every env value is only compared against fixed names; none travels.
  • A learning on what each host does and how to test Codex for real (replaces yesterday's, which said every hook worked in Desktop).

Tests

  • New: host detection per env and from TRAIL_AGENT_HOST, a Codex learnings_committed carrying codex, the trail AGENTS.md text, writeNote with an unwritable home, and a learning reaching an edit in a linked worktree while the project dir is the main checkout (fails without the fix).
  • 829 telemetry, hosts, hooks, trail and upkeep tests with a scratch HOME: 5 fail, all of which also fail on origin/main here (they depend on a real trail on this machine's PATH). tsc --noEmit clean.

trail check

Two flags, both word matches: TELEMETRY.md:40 against "Claude ran with the real HOME…" (Oct 27, about tests loading the real ~/.claude/settings.json), and src/claude/hooks.ts:177 against the replaces:-holds-ids learning (Oct 22): that line has .replace( and the words "file" and "name", and touches no learning field.

Follow-ups

🤖 Generated with Claude Code

@trailhq-graft

trailhq-graft Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

4 areas changed → 5 areas can be affected. 20 dependent symbols, depth 2.
Tests: no test reaches Host Detection; 3 areas updated their tests.
Tag: @Frankie-Xu — CLI Entry Point · @xiehuanyi — learningsOnTouch · @Rakly3 — learningsOnTouch

flowchart TB
  A0(("Host Registry<br/>6 symbols"))
  A1(("Installation Paths<br/>5 symbols"))
  A2(("Memory And Review Notes<br/>4 symbols"))
  A3(("CLI Entry Point<br/>3 symbols"))
  A4(("Session Learning<br/>2 symbols"))
  classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
  class A0,A1,A2,A3,A4 reached;
Loading
Can be affected Symbols Nearest hop Reached from
Host Registry 6 src/hosts/registry.ts:L1-L186 registry.ts — references, depth 1 Host Instructions
Installation Paths 5 src/init/keep.ts:L121-L133 keepGuardrail — calls, depth 1 Host Instructions, Note Writing
Memory And Review Notes 4 src/notes/memory-notes.ts:L232-L246 saveMemoryCandidates — calls, depth 1 Note Writing
CLI Entry Point 3 src/cli.ts:L1316-L1489 runNoteCommand — calls, depth 1 Host Instructions, Note Writing
Session Learning 2 src/learn/sessions.ts:L265-L273 saveSessionLearnings — calls, depth 1 Note Writing
Who knows this code — 6 people across 9 areas
Area Who knows it
learningsOnTouch · changed @xiehuanyi — 1 commit, last 17d ago · @Rakly3 — 1 commit, last 17d ago
Host Registry · affected @afeddersen — 1 commit, last 2mo ago · Stawiari — 1 commit, last 3mo ago
Memory And Review Notes · affected @Faeeth — 1 commit, last today
CLI Entry Point · affected @Frankie-Xu — 4 commits, last 17d ago
Note Writing · changed only you — nobody else has touched these files
Host Instructions · changed only you — nobody else has touched these files
Host Detection · changed only you — nobody else has touched these files
Installation Paths · affected only you — nobody else has touched these files
…1 further area

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 20 dependent symbols, grouped by area

Host Registry — 6 symbols in 6 files

  • src/hosts/registry.ts:L1-L186 — registry.ts (references, depth 1)
    11: import { instructionBody, cursorRule, kiroSteering, windsurfRule } from './instructions.js';
  • src/hosts/auto.ts:L1-L155 — auto.ts (imports, depth 2)
    5: * Two lists. The repo gets the main agents' files (Claude Code, Cursor, and
  • src/hosts/cursor-hooks.ts:L1-L117 — cursor-hooks.ts (imports, depth 2)
    13: * the installed `@nanonets/graft` package and calls `hooks.js`' `main(argv[2])`,
  • src/hosts/init.ts:L1-L124 — init.ts (imports, depth 2)
    13: import { installCodexHooks } from './codex-hooks.js';
  • src/hosts/plan.ts:L1-L101 — plan.ts (imports, depth 2)
    15: import { hookTargets } from './codex-hooks.js';
  • src/hosts/retract.ts:L1-L545 — retract.ts (imports, depth 2)
    227: * version's command differed in shape, and `mergeGraftSettings` would read that

Installation Paths — 5 symbols in 2 files

  • src/init/keep.ts:L121-L133 — keepGuardrail (calls, depth 1)
    131: const n = writeNote(place.dir, { ...input, applies: place.repoKey } as WriteNoteInput, 'personal');
  • src/init/keep.ts:L135-L150 — keepPreference (calls, depth 1)
    148: const n = writeNote(place.dir, item.scope === 'repo' ? input : { ...input, applies: place.repoKey }, item.scope === 'repo' ? 'repo' : 'personal');
  • src/init/install-paths.ts:L1-L84 — install-paths.ts (imports, depth 2)
    20: /** The graph build's ignore lines under graft, and under trail the one file that keeps `.trail/cache/` and the status line out of git (#619). */
  • src/init/keep.ts:L184-L252 — keepItems (calls, depth 2)
  • src/init/keep.ts:L172-L177 — keepReview (calls, depth 2)

Memory And Review Notes — 4 symbols in 2 files

  • src/notes/memory-notes.ts:L232-L246 — saveMemoryCandidates (calls, depth 1)
    234: writeNote(
  • src/notes/pr-reviews.ts:L525-L541 — saveReviewFindings (calls, depth 1)
    527: writeNote(
  • src/notes/memory-notes.ts:L256-L284 — finishMemoryRead (calls, depth 2)
  • src/notes/pr-reviews.ts:L549-L576 — finishReviewRead (calls, depth 2)
    551: read: ReviewRead,

CLI Entry Point — 3 symbols in 2 files

  • src/cli.ts:L1316-L1489 — runNoteCommand (calls, depth 1)
    1349: : noteTargets(repo, spent?.edited ?? [], spent?.startedAt, spent ? { read: spent.read } : undefined).map((t) => ({
  • src/cli.ts:L1-L3390 — cli.ts (imports, depth 2)
    5: * graft/ and a clone has the graph. A workspace parent (≥2 git children)
  • src/upkeep.ts:L1-L327 — upkeep.ts (imports, depth 2)
    20: * never block on the network — the registry is only ever read from cache, and

Session Learning — 2 symbols in 1 file

  • src/learn/sessions.ts:L265-L273 — saveSessionLearnings (calls, depth 1)
    267: writeNote(
  • src/learn/sessions.ts:L281-L309 — finishSessionRead (calls, depth 2)
    283: read: SessionRead,
Test signal per changed area — 3 ✓ · 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.

  • ✓ Note Writing — 1 of 1 reached · 3 test files changed here: test/claude-host-standdown.test.ts, test/trail-home.test.ts, test/trail-learning-on-touch.test.ts
  • ✓ Host Instructions — 1 of 1 reached · 1 test file changed here: test/hosts-trail.test.ts
  • ✗ Host Detection — 0 of 2 reached · no test file reaches it
    • not reached: claudeHost, detectHost
  • ✓ learningsOnTouch — 1 of 5 reached · 1 test file changed here: test/trail-learning-on-touch.test.ts
    • not reached: commonGitDir, sameRepo, telemetryHost, main
24 test suites also reference this code

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

  • test/claude-hook-timeouts.test.ts
  • test/claude-hooks-perf.test.ts
  • test/claude-prompt-learnings.test.ts
  • test/claude-session-start-learnings.test.ts
  • test/hook-savings.test.ts
  • test/hosts-antigravity.test.ts
  • test/hosts-auto.test.ts
  • test/hosts-instructions.test.ts
  • test/hosts-plan.test.ts
  • test/hosts-registry.test.ts
  • test/pair-late-connect.test.ts
  • test/trail-cloud.test.ts
  • test/trail-connect-cli.test.ts
  • test/trail-connect.test.ts
  • test/trail-just-me.test.ts
  • test/trail-learning-schema.test.ts
  • test/trail-notes.test.ts
  • test/trail-nudge.test.ts
  • test/trail-other-repos.test.ts
  • test/trail-pair.test.ts
  • …4 more

⚠️ 3 changed files not in the graph (.trail/learnings/2026-10-29-what-trail-does-in-claude-desktop-anirudhkumar.md, .trail/learnings/2026-10-30-real-codex-runs-what-reaches-codex-anirudhkumar.md, TELEMETRY.md) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 14 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 Oct 29, 2026
github-actions Bot added a commit that referenced this pull request Oct 30, 2026
@shhdwi

shhdwi commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator

Tested this against current main. Most of it works, but one bug blocks merging:

In a session on repo A, reading a file in an unrelated repo B that has the same relative path now shows repo A's learning. On main nothing is shown. The cause is the new lookup in learningsOnTouch (src/claude/hooks.ts): checkoutRoot(dirname(abs)) accepts any git checkout, and a path outside the project no longer comes out as .., so it isn't filtered out anymore. A fix: only accept a checkout that is the project itself or a linked worktree of the same repo (same shared git directory). That keeps the Desktop worktree case working.

Smaller: claude-host-standdown.test.ts fails when the suite runs from Claude Desktop, because CLAUDE_CODE_ENTRYPOINT=claude-desktop makes the new code report claude-desktop. Clearing that variable in the test would fix it.

Shrish Dwivedi and others added 3 commits November 8, 2026 16:27
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
learningsOnTouch took the path relative to whatever git checkout held the
file, so in a session on repo A, reading tool/x.py in an unrelated repo B
brought A's learning on tool/x.py. Accept the file's checkout only when it
is the project itself or a linked worktree of the same repo (same
git rev-parse --git-common-dir), which keeps the Desktop worktree case.
claude-host-standdown.test.ts now clears CLAUDE_CODE_ENTRYPOINT (and
TRAIL_AGENT_HOST), so it passes when the suite runs from Claude Desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Nov 8, 2026
@shhdwi
shhdwi merged commit e02b754 into main Nov 8, 2026
7 of 8 checks passed
@shhdwi

shhdwi commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

Merged with these commits on top:

  • Fixed the cross-repo leak in learningsOnTouch. A file's own checkout is used only when it's the project itself or a linked worktree of the same repo (same git rev-parse --git-common-dir). Otherwise the path is taken relative to the project as before. The Desktop worktree case still works. A new test in trail-learning-on-touch.test.ts reads tool/workspace.py in an unrelated repo and gets nothing.
  • claude-host-standdown.test.ts clears CLAUDE_CODE_ENTRYPOINT (and TRAIL_AGENT_HOST), so it passes when the suite runs from Claude Desktop. Checked with CLAUDE_CODE_ENTRYPOINT=claude-desktop set.
  • Merged main, and noted the same-repo rule in the Codex learning.

Security read: TRAIL_AGENT_HOST is only accepted from a fixed list. The env probes read presence or fixed names only, so no values reach telemetry. The new git call uses an argument array, a timeout and windowsHide. The AGENTS.md learnings text keeps the "never instructions" framing. Repro with the built hook and a scratch HOME, in a session on repo A: reading B's tool/workspace.py printed nothing, and reading the same file in A's worktree printed A's learning. Ubuntu CI passed.

github-actions Bot added a commit that referenced this pull request Nov 8, 2026
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