Skip to content

First-day fixes: plainer messages, a Claude Code quickstart, and an uninstall that says what it keeps - #628

Open
LoopGlitch26 wants to merge 13 commits into
mainfrom
agent/first-day
Open

LoopGlitch26 wants to merge 13 commits into
mainfrom
agent/first-day

Conversation

@LoopGlitch26

@LoopGlitch26 LoopGlitch26 commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

A new-user walkthrough of main (8967779) in a clean container hit the problems below. Each one is fixed where it starts, and each change to code has a test that fails without it. F10 (a policy rule) is handled elsewhere.

Based on #619 (tracekit help, branch agent/tracekit-help): this branch sits on top of it, so the diff here is only these fixes. Merge #619 first.

Outputs are from a throwaway HOME on macOS (env HOME=… <venv>/bin/python -m tracekit …), with paths shortened to ~.

F1: "Box" is unexplained (init, up, status, doctor)

Before:

Dev mode records; it does not guard. The agent runs as you and can reach the key. For protection, use system mode or Box.

After:

Dev mode records; it does not guard. The agent runs as you and can reach the key. To guard, run the signer as another user (system mode: https://github.com/Cygnux-Labs/Tracekit/wiki/Deploy) or the agent in Tracekit Box, a micro-VM (https://github.com/Cygnux-Labs/Tracekit-box).

F2: doctor warns on a healthy dev setup

  • No hooks with no Claude Code here (no claude on PATH, no ~/.claude) is info, not WARN. The witness check is info in dev. Only D-PROCESS-BOUNDARY, which is what dev mode is, stays a WARN. --json gains the status info (docs/doctor.md says so).
  • The system-mode fix names the wiki instead of docs/deploy.md. It names doctor's own Python when only root can change it (init refuses any other), and /usr/bin/python3 otherwise (a user's venv, or /opt/tracekit's venv, which init can't run from on 3.9 or 3.10).
  • A healthy dev run ends with one line saying so.
  • Root cause found along the way: D-SIGNER-ENV read the settings file without handling a missing one, so doctor crashed once uninstall deleted it. It now reads it as _setting does.

Before:

WARN  Dev mode records; it does not guard. ... For protection, use system mode or Box.  (D-PROCESS-BOUNDARY)
      fix: use system mode where the agent must not reach the signer: from a root-owned clone of the release (docs/deploy.md#laptop-system-mode), sudo /usr/bin/python3 -m tracekit init --v2 --user <agent-user> --experimental-macos
...
WARN  no witnesses: checkpoints are not cosigned, so assurance stays dev  (D-WITNESS-CONFIGURED)
ok    ~/Library/Application Support/tracekit/data is on apfs  (D-DATA-FS)

After:

WARN  Dev mode records; it does not guard. ... or the agent in Tracekit Box, a micro-VM (https://github.com/Cygnux-Labs/Tracekit-box).  (D-PROCESS-BOUNDARY)
      fix: use system mode where the agent must not reach the signer: from a root-owned clone of the release (https://github.com/Cygnux-Labs/Tracekit/wiki/Deploy#laptop-system-mode), sudo /usr/bin/python3 -m tracekit init --v2 --user <agent-user> --experimental-macos
...
info  no witnesses: checkpoints are not cosigned, so assurance stays dev  (D-WITNESS-CONFIGURED)
      fix: the dev signer takes no witnesses: serve one from a signer.yaml with witnesses: (https://github.com/Cygnux-Labs/Tracekit/wiki/Witnesses)
ok    ~/Library/Application Support/tracekit/data is on apfs  (D-DATA-FS)
dev setup OK: everything checks out for dev mode, which records but does not guard (D-PROCESS-BOUNDARY)

With no Claude Code: info no Claude Code here (no claude on PATH, no ~/.claude), so no hooks: runs that SDK clients send (tracekit.instrument()) are still recorded (D-HOOKS-PRESENT).

F3: init --dev --v2 gives no next step, and its link has no target

The anchor #when-tracekit-stops-a-call isn't on the published wiki yet (it arrives with the next release). The line now points to the file the plugin ships those lines in, which is on GitHub today.

Before:

when Tracekit holds or blocks a call, the agent should tell you and pass on the commands: paste the lines at https://github.com/Cygnux-Labs/Tracekit/wiki/Coding-Agents#when-tracekit-stops-a-call into its CLAUDE.md or AGENTS.md

After:

when Tracekit holds or blocks a call, the agent should tell you and pass on the commands: paste the lines at https://github.com/Cygnux-Labs/Tracekit/blob/main/plugin/skills/tracekit-holds/SKILL.md into its CLAUDE.md or AGENTS.md
next: start a new Claude Code session (one already open doesn't load the hooks). A call the policy holds shows a `! tracekit approvals approve <id>` line: paste it into that session to allow the call once, then ask the agent to retry it. See every run: tracekit view --dev

With no claude on PATH the line starts next: install Claude Code (no claude on PATH yet), then start a session.

F4: the framework quickstarts

LangChain's example called create_agent("openai:gpt-5", ...) without langchain-openai (ImportError: Initializing ChatOpenAI requires the langchain-openai package) or a word about OPENAI_API_KEY. Each quickstart (LangChain, OpenAI Agents, Claude Agent SDK, MCP) now:

  • leads with Try it first, with no API key: clone, venv, pip install . plus the framework, then python examples/v2/<x>/agent.py --scripted. All four ran here with no OPENAI_API_KEY or ANTHROPIC_API_KEY set and printed Integrity: VERIFIED.;
  • names what the live example needs: LangChain langchain-openai and OPENAI_API_KEY (or another provider's package and key); OpenAI Agents OPENAI_API_KEY; Claude Agent SDK ANTHROPIC_API_KEY (it runs the Claude Code it bundles); MCP needs no key, and my-mcp-server is your server's command.

F5: tracekit last found no runs after instrument()

Before:

tracekit last: no runs in the dev signer's store (~/…/store) yet: add `import tracekit; tracekit.instrument()` to your agent and run it

After:

tracekit last: no runs in the dev signer's store (~/…/store) yet. `tracekit.instrument()` records only the frameworks it lists, and says so when it finds none of them (https://github.com/Cygnux-Labs/Tracekit/wiki/Quickstart#2-one-line-in-your-agent); for any other agent, record its calls as https://github.com/Cygnux-Labs/Tracekit/wiki/Quickstarts-Custom shows. Claude Code: a session started after `tracekit init --dev --v2` records its calls.

The First-run errors row in quickstart-v2 says the same.

F7: no Claude Code setup page

New docs/quickstarts/claude-code.md: install and init, start a new session, what a deny and a hold look like (copied from real hook output), answering with !, the retry, tracekit last / last --open / view --dev, and uninstall. It only states what the code does. Linked from docs/integrations.md and the quickstart index. The README link is drafted in the report, not edited here.

F8: tracekit pending with v2 holds and no v1 signer

Before:

$ tracekit pending
signer unavailable: [Errno 2] No such file or directory

After (no v1 signer configured, and none at the default socket):

$ tracekit pending
apr-5733…  requested  run=8943…  tool="Bash"  rules=TK-SHELL-PARSE  expires 2026-11-05T15:20:40.046351Z (in 60 min)  "echo \"unterminated"

F9: uninstall is silent about what it keeps

Before:

$ tracekit uninstall
Tracekit v2 hooks removed from ~/.claude/settings.json

(and settings.json, which init had made, stayed behind holding {})

After:

$ tracekit uninstall
Tracekit v2 hooks removed from ~/.claude/settings.json, and the file with them: init made it, and nothing else is in it
kept:
  ~/Library/Application Support/tracekit/data  (the dev signer's keys and signed runs: a bundle you exported verifies without them)
  ~/.tracekit-client  (Tracekit's client config)
  /tmp/tk-501-347b7ec8d4ab  (the dev signer's runtime dir)
to delete these and Tracekit too:
  tracekit down
  rm -rf '~/Library/Application Support/tracekit' ~/.tracekit-client /tmp/tk-501-347b7ec8d4ab
  ~/.venv/bin/python -m pip uninstall tracekit-ai
  • Init marks a settings file it creates (_tracekit_created, like the existing _tracekit_thinking). Uninstall deletes that file only when nothing else is left in it. A file you had before stays.
  • The runtime dir it names is this HOME's own. The first version named the per-user /tmp/tk-501, which another HOME's live signer was using. autospawn.own_runtime_dirs() leaves out a dir whose endpoint.json names another data dir.
  • uv tool and pipx installs get uv tool uninstall / pipx uninstall. Windows gets Remove-Item -Recurse -Force.
  • Help: remove hooks (the ledger is kept) → remove Tracekit's hooks (your signed runs and keys stay: it says where, and how to delete them too).
  • quickstart-v2 has a Remove it section.

F11: approvals show, the parser's cause, the ! hint

approvals show used TK-SHELL-PARSE's general text where the hook used the decision's cause. It now runs the same engine on the signer's copy of the arguments to get that cause. An unclosed ', " or $' now has its own cause, quote (cause texts in compile.py and the error class in the parser; the verdict is unchanged, still ask). The Claude Code ! hint shows only for runs a Claude Code session on this machine owns (the v2 hook's session state names the run).

Before (hook, then approvals show):

[tracekit] HELD, nothing ran: apr-928e… (TK-SHELL-PARSE), expires 14:26 UTC.
Why: the line uses shell syntax the policy doesn't read
Instead: Split it into plain commands; pipes, &&, if, for, case and functions are read.

why         the policy can't read what this shell command runs (TK-SHELL-PARSE)
instead     Name the program itself (`git push …`, not `$g push …`) so the policy can read it.
expires     2026-11-05T14:26:40.046351Z (in 59 min)
answer      `tracekit approvals approve apr-928e…` to allow it once, …; in the agent's Claude Code session, with a leading `! `

After:

[tracekit] HELD, nothing ran: apr-5733… (TK-SHELL-PARSE), expires 15:20 UTC.
Why: a quote on the line is never closed
Instead: Close the quote.

why         a quote on the line is never closed (TK-SHELL-PARSE)
instead     Close the quote.
expires     2026-11-05T15:20:40.046351Z (in 59 min)
answer      `tracekit approvals approve apr-5733…` to allow it once, …; in the agent's Claude Code session, with a leading `! `

For an SDK agent's run the answer line has no ! part.

F12, F13: the viewer

--approval starts the viewer and prints a link that opens the held call, and that is what people want from approvals show's record line (a printed link to a viewer that isn't running opens nothing). So the help now says what it does.

Before:

--approval ID         print the link that opens the call approval ID held (`tracekit approvals show ID` names it)
--host HOST
--port PORT
tracekit view on http://127.0.0.1:7778/?token=…  (store: ~/…/store, read-only; trust pins this store's own log.vkey: dev assurance, the viewer runs as the same user as the dev signer and proves only that the records match that key)
  graphs: each verified run, imported from the store (read-only) into a temp dir

After:

--host HOST           the address to serve on (default 127.0.0.1: this machine only; another one needs --tls-cert and --tls-key)
--port PORT           the port to serve on (default 7778; 0: any free one)
--approval ID         start the viewer and print a link that opens the call approval ID held (`tracekit approvals show ID` names it)
tracekit view on http://127.0.0.1:57355/?token=<token>#approval=apr-5733…  (open it in a browser; Ctrl-C stops the viewer)
  reading the store ~/…/store, read-only; each run is checked against the key this store names, so it shows the records weren't changed after signing, not that the agent couldn't have signed them: in dev mode it runs as you, as the signer does
  graphs: each verified run is drawn as a graph too, from a copy in a temp dir

(the second run was --host 0.0.0.0 --insecure-http: the link is 127.0.0.1; before, the line printed the --host value as given, so http://0.0.0.0:…)

F14: an empty run

Before: VERIFIED: 5 signed records, none changed, removed or added after signing.
After: VERIFIED: no tool calls recorded; 5 signed records, none changed, removed or added after signing.
Exit codes and --json are unchanged (tested).

F15: tracekit status

Before: a page of JSON (45 lines here). After:

signer      dev signer 1.5.3, running (pid 82338), socket /tmp/tk-501-347b7ec8d4ab/signer.sock
data        ~/Library/Application Support/tracekit/data (its keys and signed runs)
hooks       v2 hooks in ~/.claude/settings.json (9 events)
Dev mode records; it does not guard. …
(tracekit status --json prints all of it)

--json prints the JSON as before. The one test that parsed it and docs/portability.md now use --json. Nothing else in the repo reads it.

F16: the scripted snippet's approval wait

Before: if run.call("approval_wait", ...)["state"] == "approved" and ...: with nothing on timeout. After: the snippet keeps the state and prints call-3 not run: no answer within 5 minutes when the wait ends with the call still requested.

F17: the hook command was base64

Before (a checkout or pip install --user):

/bin/sh -c '…/python -I -c '"'"'import base64,sys; r=base64.b64decode('"'"'"'"'"'"'"'"'L3ByaXZhdGUv…'"'"'"'"'"'"'"'"').decode(…

After:

/bin/sh -c '…/python -I -c '"'"'import sys; r = sys.argv[1]; sys.path.insert(0, r); import os, shlex, shutil, subprocess; w = sys.argv[2]; n = lambda: subprocess.run(["git", "-C", r, "symbolic-ref", "-q", "--short", "HEAD"], …).stdout.strip() or "a detached HEAD"; sys.excepthook = lambda t, e, b: print(f"tracekit: the hook runs Tracekit from the working tree {r}, …", file=sys.stderr) if shutil.which("git") else sys.__excepthook__(t, e, b); from tracekit.integrations.claude_code import _entry; sys.excepthook = sys.__excepthook__; raise SystemExit(_entry(*sys.argv[3:]))'"'"' ~/src/Tracekit main PreToolUse; s=$?; …'

The tree, the branch and the event are quoted arguments after the code, so the code holds no path. Behaviour is the same: the branch-switch message (the existing WorkingTree test passes unchanged, including the no-git case), fail-closed wrapping, and the event name. A new test runs the hook from a path with a space, both quotes and $HOME in it. doctor's D-VERSIONS probe reads the tree from the new form and still reads the old base64 form in hooks already installed. docs/quickstart-v2.md and plugin/README.md describe the command.

F18: quickstart-v2 step 2

Step 2 is now the one line, what instrument() records (and what to do when it finds nothing), how a held call looks from an SDK agent, and links to Claude Code and TypeScript. The server pattern, the 17-row wait table and the socket internals moved under Deeper: drive the signer yourself (the headings and their anchors are unchanged). Also new: a Remove it section, the Claude Code quickstart in the per-framework list, and tracekit status mentioned in step 3.

Help text (tracekit help, #619)

up: start the local signer (dev mode), or find the running one; down: stop the local signer (dev mode); policy: check a v2 policy and try it on past runs; status: what is set up here: hooks, signer, policy, capture sources; ingest, otel, daemon say (experimental); observe: the old name of tracekit view --ledger: the v1 ledger or a v1 bundle in a read-only web page; uninstall as above. tests/test_cli.py pins none of these.

Nits

  • demo --server: Bash 'ls': allow (trailing space) → Bash 'ls': allow. approved apr-… (self-approved: True) → approved apr-…, by the run's own user: a self-approval, so the run's assurance stays dev.
  • Expiry: approvals list and approvals show print the signer's ISO time plus the minutes left, 2026-11-05T15:20:40.046351Z (in 59 min) (main's _expires_left since CLI: print remaining time on approvals list as show does #627, which this branch now uses). The hook's held line keeps 15:20 UTC alone (claude_code.expires): harnesses such as Cursor cut that message at 500 characters, and the extra words pushed the remedy past the cut (an existing test caught it), and a transcript keeps the line after the minutes have passed. The viewer's approval page keeps its local-time display; v1 pending keeps its seconds.
  • report: TK-F004 1 side_effect → TK-F004 1 side effect (a rule's label is shown with spaces, in the viewer too).
  • last says tracekit last: run r1 (a) just closed; waiting up to 15 s while the signer finalises it once, instead of waiting in silence.
  • README <run id>.trust.json wording: drafted in the report, not edited here.

Checks

  • Touched test files pass. New regression tests: ClearText (test_hook_invocation), FirstDay and test_the_reinit_fix_names_the_wiki_and_a_python_init_accepts (test_doctor_v2), TestFirstDayCli (test_signer_approvals: show cause, ! hint, pending), test_uninstall_deletes_the_settings_file_init_made_and_says_what_it_keeps (test_hook_v2), test_uninstall_names_only_this_homes_runtime_dirs (test_signer_dev), test_status_prints_a_summary_and_json_only_with_json (test_dev_mode_line), test_the_start_lines_are_plain_and_the_link_is_one_a_browser_opens, test_last_says_it_waits_for_a_run_that_just_closed and label/no-runs assertions (test_view), the empty-run summary (test_verify_short), the quote cause (test_policy2).
  • Full gates in the worktree (PYTHONPATH=$PWD TRACEKIT_PYTHON=<venv python> make test-ts lint build test): test-ts 150 passed, 0 failed; lint clean; build OK; 4452 passed, 394 skipped, 10 xfailed. scripts/wiki.py builds the wiki (128 pages) with every link and anchor resolved.
  • The first full run found 20 tests that pinned the old behaviour (status as JSON, a settings file that stays after uninstall, a 500-character hold message). Each was updated, except the 500-character one: it caught a real regression, so the held line keeps HH:MM UTC alone.
  • Not checked: the viewer page in a browser (only its start lines changed, plus the label text it shows), Windows (the Remove-Item line and the hook command's Windows quoting are covered by reading the code and the existing IsolatedCommand test only), Linux (CI runs it).

`tracekit --help` printed every subcommand as one flat list in the order
they were registered, and `tracekit` alone was an argparse error.

- `tracekit help` and a bare `tracekit` print an overview: each command
  under what a person runs it for, one entry each from its own help text
  (up to where its usage starts), experimental ones marked as their help
  marks them, then the features that are not commands (witnesses and
  anchoring, Tracekit Box, the SDKs and integrations) with their pages.
- `tracekit help NAME` is `tracekit NAME --help`; an unknown name lists
  the commands and exits 2.
- The parser is built in `_parser()` so the overview and the tests read
  the registered commands; tests/test_cli.py checks every command is in
  exactly one group.
- docs/quickstart-v2.md names `tracekit help`.
…ninstall that says what it keeps

A new-user walkthrough of main in a clean container found these; each is fixed at its cause.

- The dev-mode line says how to guard (system mode, Tracekit Box), with links.
- doctor: no Claude Code and no witness are info in dev, not warnings; a healthy dev run ends
  with "dev setup OK"; the system-mode fix names the wiki and a Python init accepts.
- init --dev --v2 ends with a next step, and points to lines that exist today.
- tracekit last with no runs says instrument() records only the frameworks it lists.
- Framework quickstarts lead with their no-key --scripted route and name what the live
  example needs (langchain-openai and OPENAI_API_KEY, ANTHROPIC_API_KEY).
- docs/quickstarts/claude-code.md: init, a new session, a hold, answering with !, retry,
  last, view, uninstall.
- tracekit pending lists the v2 signer's held calls when there is no v1 signer.
- uninstall lists what it keeps with the commands that delete it, deletes a settings file
  init made that is now empty, and its help no longer says "ledger".
- approvals show gives the cause the hook gives; an unclosed quote has its own cause; the
  Claude Code `!` hint is for Claude Code runs only; list and show give expiry as UTC time
  and minutes left.
- view: --approval's help says it starts the viewer; plain start lines; a clickable
  127.0.0.1 link for an any-address bind; --host and --port help.
- verify: an empty run says "no tool calls recorded".
- status prints a summary; --json keeps the JSON.
- The hook command for a checkout or --user install is clear text, not base64.
- quickstart-v2: a short step 2, the server pattern, the wait table and socket details under
  Deeper, a Remove it section, and the scripted snippet says when the wait ran out.
- Help text for up, down, policy, uninstall, observe; ingest, otel and daemon say experimental.
- Nits: demo --server's approval line, report's side_effect label, last says when it waits.
… doctor, what uninstall names, status

- Windows: the hook command passed the tree, branch and event as arguments quoted for the C
  runtime, which Git Bash, PowerShell and cmd.exe read as shell text (backslashes lost, a quote
  breaking the line, a branch `fix&calc` or `f$(id)` running a command). They are Python
  literals inside the double-quoted code now, every character those shells read escaped
  (install._literal); doctor reads the tree back from them. POSIX keeps quoted arguments.
- doctor took a `"` off every word of a POSIX hook command; only Windows' split keeps them.
- uninstall named the client config's signer_home in any mode: on a v1 system mode host,
  /var/lib/tracekit. Only a dev mode's home, never under a system config, as uninstall()
  stops it.
- uninstall named a shared per-user runtime dir with no endpoint.json as this HOME's. It
  names only dirs provably this HOME's and says when the shared one can't be told.
- status crashed on a settings file it can't read and printed "(0 events)" for one without
  Tracekit's hooks; it says which file it can't read and how to add hooks.
- approvals list and show use #627's _expires_left, so the two branches agree.
…elp's own description

From #619's review:
- The overview cut each help at the first `; ` or `: \``, which dropped
  audit's "read-only, not a signed record". It now cuts only where usage
  starts: a `; ` or `: ` before a backquote, `--` or a URL.
- daemon, observe, ingest, otel and proxy (the v1 path) move to a
  "Legacy (v1)" group, so they don't read as current server tools.
- `tracekit help causeway|sql|proofpack` gives the "moved to a separate
  package" answer `tracekit causeway` gives.
- `tracekit help help` has a description and its argument a help line.
- Root-only SystemV2Install expected purge to leave {} in the project's
  settings; uninstall now deletes a settings file init made when nothing
  else is in it, as the ClaudeConfigDir tests already expect.
- own_runtime_dirs' test set HOME only; Windows' ~ is USERPROFILE, and its
  temp dir is inside the profile, so the shared dir read as this HOME's.
- Reinit wrote /opt's venv Python with / separators; it is install.OPT_PYTHON,
  which doctor matches with os.sep.
Base automatically changed from agent/tracekit-help to main November 6, 2026 10:03
Conflicts: the quickstart's moved sections take #625's NOT ANCHORED and
fingerprint lines; the no-hooks test keeps #625's --no-witness checks with
this branch's removal message; approvals use main's state-aware
_expires_left (#627) in show and list.
…ines

- doctor: the dev signer uses the public witness by default since #625; its
  D-WITNESS-CONFIGURED info is for none until a release names it, or after
  --no-witness (docs/doctor.md, the DEV_INFO comment, test comments).
- uninstall on Windows: the runtime dir inside the data dir isn't named again
  in Remove-Item, and the pip line starts with & so PowerShell runs a quoted
  Python path; a test checks both printed commands.
- a test for demo --server's self-approval line; view's label comment says
  what it means.
Conflicts in tracekit/view.py and tests/test_view.py: the viewer keeps this
branch's --approval help and start lines, and takes #609's --transcripts
option and tips line after them; both tests stay.
…s line

- doctor: before the dev signer's first start (no store yet, as right after
  init) the content key, record key and key hygiene rows are info and say
  they're checked after that start, so doctor straight after init --dev --v2
  ends with dev setup OK, as the Claude Code quickstart says.
- status: names the Claude Code plugin's hooks, and a --no-hooks choice
  (dev or system mode), instead of telling both that init adds hooks.
test_content_vault's doctor test turned the vault on and expected warn with no
key yet, in a data dir no signer had used: dev now reports that as info until
the first start (test_doctor_v2 covers it). The test gives it the store a
started signer leaves, so it checks the warning it was written for.

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