2026-09-06 Declaratively Syncing CLAUDE.md to claude.ai Instructions
What I set out to do
Answer a small question: is there a way to sync the top-level ~/.claude/CLAUDE.md to claude.ai the way skills sync server-side? It turned into shipping a declarative sync.
What I actually did
- Confirmed there is no CLAUDE.md sync. The only claude.ai to Claude Code channels in the binary are
syncClaudeAiSkills/syncClaudeAiPluginsand managed (enterprise) settings; nothing memory- or instructions-shaped. - Found the account-wide “Instructions for Claude” field is
conversation_preferencesonclaude.ai/api/account_profile, by reading the web bundles rather than the docs. - Chased whether the Claude Code OAuth token could write it. It cannot:
api.anthropic.com/api/account_profilereturnsoauth_token_not_accepted, and/api/oauth/profileplus/api/oauth/account/settings(which the token does read) do not carry the field. I was wrong twice along the way, first “browser-only” then “a cookie will not work from curl”, and testing corrected both. - Reconstructed the live Claude Desktop session from disk: decrypt its Chromium cookie jar (Safe Storage key from the keychain, PBKDF2 then AES-128-CBC), and replay the real
cf_clearancecookie to clear Cloudflare. No fingerprint spoofing; it is my own session. - Captured the exact write shape by intercepting the web UI’s own
fetch:PUT {"conversation_preferences": "<text>"}, a single-field partial update. My earlier guessed four-field payload (which would have nulledfull_name) was wrong, and capturing is why nothing got clobbered. - Shipped the sync:
nix/scripts/claude-ai-instructions-sync.pyplus a Darwin-gated activation module. Source of truth is the sameAGENTS.mdthat deploys toCLAUDE.md; only the Communication Style and Writing Style sections are pushed (the rest names Claude Code tools that do not exist in a chat). Best-effort, hash-gated, keychain read via the stable/usr/bin/security.just checkandhm buildgreen, committed as3869049.
What was striking
- The keychain-grant-binds-to-binary constraint (the same one behind 2026-07-25 Obsidian MCP TCC Break from a Homebrew node Upgrade) flipped the mechanism. The obvious launchd agent (the gmailctl pattern) cannot show the approval prompt, and its grant would not survive nix store-path churn, so the sync runs as an interactive
hm switchactivation step instead. Reading via/usr/bin/security, a path that never moves, is what makes a one-time “Always Allow” stick. - Replaying my own
cf_clearancegot plain curl past Cloudflare cleanly. The edge was not the wall I assumed. - The whole thing is a live-session tap, not a durable credential: it depends on Claude Desktop staying logged in. Fine for a switch-time push, and deliberately not a background job.
Next
- Run
hm switchinteractively once to grant the keychain and push the first sync. - Decide later whether the Knowledge Capture section should sync (the vault MCP connector exists on claude.ai, but it references the
/noteskill that does not).
Related
- 2026-07-26 Publishing Local Claude Skills to the claude.ai Store: the other direction of the claude.ai / Claude Code boundary.
- 2026-07-25 Obsidian MCP TCC Break from a Homebrew node Upgrade: the binary-bound-grant failure mode that shaped the mechanism.