Connect Claude Code¶
For synchronous workspace policy, local context and response checks, start with Live agent connections. That installer preserves the activity recording hooks described below. Sign-in and recording alone do not activate the synchronous loop.
On a Mac, start with Planisphere for Mac: its Memory view connects Claude Code and installs its memory hooks. The recording hooks on this page add activity reporting to your console workspace; after merging them, quit and relaunch Claude Code so they load.
The Claude Code hooks do two things.
- They record. Each prompt you submit, each tool call Claude makes and each reply it finishes becomes a sealed record. Prompts, tool inputs and replies are hashed with SHA-256 on your machine; Planisphere receives the hashes, not the text. These hooks send in the background and never stop Claude Code.
- They guard your boundaries. Before a tool runs, the crossing guard checks the call against the boundary policies your workspace saved on the console's Policies page. A call that crosses none goes ahead and sends nothing more. A call that crosses one asks Planisphere first, and a Hold or Wall policy stops it. See Boundary policies.
You can also opt in to Usage by prompt, a separate companion that measures token usage and visible UTF-8 text sizes locally, then reports private observations in Activity. Its optional prompt bridge retains an exact link to the original prompt receipt. The guide explains project scope, source attribution and incomplete counts.
The console writes the hooks for you. On the Connections page, under Connect a system, choose Claude Code, paste a key, and copy the block labelled Claude Code hooks (settings.json). Read the commands before you paste them anywhere.
What the hooks record¶
Every record uses the AI Provenance pack and the system name
claude-code-primary.
| Record (hook) | Hashed on your machine | Sent as plain metadata |
|---|---|---|
prompt_submitted (UserPromptSubmit) |
The prompt text | Model, session ID, working directory, agent type, a prompt counter |
tool_used (PreToolUse, every tool) |
The tool's input, as compact JSON | Tool name, tool class, session ID, agent type |
agent_response (Stop) |
Claude's latest reply text, and the whole transcript file | Model, session ID, stop reason, working directory, turn count, a reply counter |
The fourth recording hook, SessionStart, sends nothing. It saves the session's
model name in /tmp/planisphere/ so the prompt records can carry it. The
crossing guard adds two more entries, one beside SessionStart and one beside
PreToolUse; the next section describes them.
The tool class groups tool names: fs_read (Read, Glob, Grep), fs_write
(Write, Edit, NotebookEdit), shell (Bash, BashOutput, KillShell), network
(WebFetch, WebSearch), agent_dispatch (Task, Agent), skill (Skill) and
other.
A few limits follow from where the hooks run:
- Tool calls are recorded before they run. PreToolUse fires as Claude
asks for the tool, so a
tool_usedrecord says the call was requested. Itssuccessispendingand itsduration_msis0: placeholders, not measurements. Tool output is not recorded. - The working directory is sent as written. Prompt and reply records carry the full path of the folder Claude Code runs in. If a path names a client or a project you keep private, use a neutral folder name.
- The Stop hook reads your transcript locally. It finds Claude's latest reply in the session transcript and hashes it, and it hashes the transcript file as a whole. Neither leaves your machine as text.
- The counters are per machine.
system_seqcomes from~/.claude/planisphere-seq-promptand~/.claude/planisphere-seq-response, which the hooks increment. Two machines keep separate counts.
Boundary policies¶
A boundary policy names what crosses a boundary, who guards it and how Planisphere responds: Watch, Flag, Hold or Wall. You write them on the console's Policies page, starting from a starter or from nothing. Only a saved policy is enforced.
What crosses is one or more patterns, where * matches anything:
| Pattern | Matched against | Example |
|---|---|---|
| Commands | Each statement of a Bash command. cd repo && git push origin main is two statements. Leading env, sudo, time and VAR=value are ignored. |
git push*main* |
| Files | The file a tool reads or writes. A name without / matches the file's name; with /, the end of its path. Case is ignored. |
.env*, config/*.pem |
| Web | The host of an address a tool fetches. Case is ignored. | api.stripe.com, *.stripe.com |
| Tools | Any use of a tool, by its name. | mcp__*stripe* |
How the guard works:
- At session start, and then at most once a minute, it fetches your saved
policies into
~/.claude/planisphere/rules.json(or into$PLANISPHERE_HOOK_DIR, if you set it). - Before each tool runs, it checks the call against that copy, on your machine. A call that crosses no saved boundary goes ahead, and nothing more is sent.
- A call that crosses one asks Planisphere. It sends that call's command,
file path or web address, and a SHA-256 of the whole tool input. It never
sends file contents, edit text or tool output. Planisphere checks the
call against the policies in force and seals the decision:
- Watch lets it through and records the crossing.
- Flag lets it through, records it, opens a review for the guard, and tells Claude it was flagged.
- Hold stops it and tells Claude who it is waiting on. The guard approves or rejects it in Reviews, signed in. After an approval, the identical request goes through once: the same tool and input, in the same folder and session. The same request again is held again.
- Wall stops it every time. No one can approve it.
- Only a person signed in to the console decides a held crossing: the person or role the policy names as its guard. A workspace key, like the one in these hooks, can't approve one, and can't save or remove a policy.
The command, path or address is kept as a readable fact of the crossing only while your workspace keeps readable details (Account). Otherwise only its hash is kept, and the guard decides without seeing it.
If Planisphere can't be reached (5 seconds, or 2 to connect), a call that
crosses a Hold or Wall boundary is refused, and Claude is told why. A
Watch or Flag crossing goes ahead and is kept in
~/.claude/planisphere/spool.jsonl, then sent at the next session start.
The guard never answers "allow": a call it lets through still goes through your own Claude Code permission settings. A policy you save reaches a running session within a minute.
The guard holds a cooperative agent to your policies; it doesn't contain one
that works around them. It matches the text of a command, so a command
wrapped in another shell (sh -c '…') or a script file can pass a pattern
the plain command would cross. Anyone who edits ~/.claude/settings.json can
remove the hooks. The witnesses on Connections, such as GitHub and your
cloud audit log, are the independent check on what actually crossed.
Before you start¶
- jq. The hooks use it to read each event Claude Code passes them. Check
with
command -v jq; on a Mac,brew install jq; on Debian or Ubuntu,sudo apt-get install jq. - curl and a SHA-256 tool. macOS has
curlandshasum; most Linux systems havecurlandsha256sum. The hooks use whichever is present. - A key from your workspace. Create one on the console's Keys page.
Use a key just for this machine, for example
claude-code-laptop, so you can revoke it without touching anything else. - macOS or Linux. Claude Code runs each hook command with
sh -c: bash 3.2 in POSIX mode on macOS, dash on Debian and Ubuntu. The hooks are written for that plain POSIX shell, not for a newer bash.
The key ends up in your settings file as plain text, inside each hook command. Keep that file private, and never put these hooks in a settings file you commit to a repository.
Add the hooks¶
- In the console, open Connections and choose Claude Code under Connect a system.
- Paste your key into Key to fill in. It stays in that browser tab and fills into the hooks; nothing is sent.
- Copy the Claude Code hooks (settings.json) block.
- Open
~/.claude/settings.json, your user settings, which apply to every project on this machine. If the file doesn't exist, create it with the block as its whole content. - If the file already has a
"hooks"object, merge rather than replace: for each ofSessionStart,PreToolUse,UserPromptSubmitandStop, add the Planisphere entry to the event's list next to the entries you already have. Check the result is valid JSON withjq . ~/.claude/settings.json. - Start a new Claude Code session. Claude Code's
/hooksmenu lists the hooks it loaded. - To have Claude Code ask before crossing a boundary, save a policy on the console's Policies page.
To connect a single project instead, put the hooks in that project's
.claude/settings.local.json, your own settings for that project, which
are not meant to be committed. Avoid .claude/settings.json in a project:
it is shared through the repository, and the hooks contain your key.
Check that records arrive¶
Send a prompt in the new session, then open the console:
- Connections → Systems sending records lists
claude-code-primarywith its record count for the last 24 hours, when the last record arrived and its last event. The last event opens that record. - Activity lists the records your workspace sealed, newest first. Each opens with its receipt.
The hooks don't print action keys, so start from the Systems table rather than Check a received record. One received record shows that the hooks ran and reached your workspace once; it doesn't show that every prompt or tool call was captured.
Troubleshooting¶
Nothing arrives. The hooks send in the background and discard errors, so they never show a failure themselves. Planisphere does when it can tell whose records it refused: a revoked key still in use, the sandbox's record cap, or invalid or rate-limited records put a red "records refused" banner on the console's Connections page and a warning on its Connections tab, and the workspace's owners get an email, at most once a day for each problem. A key Planisphere never issued names no workspace, so that failure stays silent. Check, in order:
command -v jqprints a path.jq . ~/.claude/settings.jsonprints your settings, not an error.- You started a new session after adding the hooks, and
/hookslists them. -
The key is current. This prints
200for a working key and401for a revoked or mistyped one:curl -sS -o /dev/null -w '%{http_code}\n' https://api.planisphere.ooo/tenant/me \ -H "X-Planisphere-Key: YOUR_KEY"
Prompts and replies arrive, but no tool calls, or Claude Code reports a
hook error on every tool. Your hooks are an older copy. Claude Code runs
hook commands with sh -c, and on macOS that is bash 3.2 in POSIX mode,
which can't parse a case pattern's closing ) inside $(...). The old
PreToolUse command then stops with syntax error near unexpected token and
exit status 2, which Claude Code treats as a block on that tool call. Copy
the hooks again from Connections, or edit the PreToolUse command so each
pattern opens with ( as well:
CLASS=$(case "$TN" in (Read|Glob|Grep) echo fs_read;; (Write|Edit|NotebookEdit) echo fs_write;; (Bash|BashOutput|KillShell) echo shell;; (WebFetch|WebSearch) echo network;; (Task|Agent) echo agent_dispatch;; (Skill) echo skill;; (*) echo other;; esac);
Some prompts or tool calls never arrive, often the ones with line breaks
or backslashes. Your hooks are an older copy that passes each event to jq
with echo "$INPUT". In sh (bash 3.2 in POSIX mode on macOS, dash on
Debian and Ubuntu), echo turns the \n and other backslash escapes inside
the event's JSON into real characters, so jq can't read it and the record is
silently dropped. Copy the hooks again from Connections: the current
hooks pass each value with printf '%s\n', which prints it unchanged. To
fix a copy by hand, replace every echo "$INPUT" and echo "$LAST" with
the same printf form, for example:
SID=$(printf '%s\n' "$INPUT" | jq -r .session_id);
Older Stop hooks fail differently: instead of dropping the record, they send a
reply record whose response hash is the hash of empty text
(sha256:e3b0c442…) and whose model is blank. Records like that, sealed
before you updated the hooks, show an empty reply, not the real one.
A tool call is refused with "Planisphere couldn't be reached". It crosses a Hold or Wall boundary, and the guard couldn't get an answer: no network, a revoked key, or Planisphere unavailable. Check the key with the command above. The call goes through once Planisphere answers and the policy allows it.
A policy you saved isn't asked about yet. The guard's copy of your
policies refreshes at session start and at most once a minute. Start a new
session, or wait a minute. Check that the pattern matches the call as Claude
Code writes it: patterns match each statement from its start, so git push*
doesn't match git -C repo push.
Records stopped after a while. A sandbox workspace keeps up to 100
records, and the hooks send one for every prompt, tool call and reply, so a
working session reaches that quickly. After that the API answers 402 and
the hooks drop the record. Upgrade on the console's Billing page.
The model shows as unknown. The prompt record reads the model name that
SessionStart saved in /tmp/planisphere/. If that file is missing (the
session began before you added the hooks, or /tmp was cleared), the record
says unknown. A new session fixes it.
Remove the hooks¶
Delete the six Planisphere entries from ~/.claude/settings.json (or from the
project file you used), then start a new session. Remove
/tmp/planisphere/, ~/.claude/planisphere/ and ~/.claude/planisphere-seq-*
if you no longer want them, and revoke the key on the console's Keys page.
Hook events and settings files follow the official Claude Code hooks documentation.