Transparency and modding tool for Claude Code
Project description
ccmod
Claude Code behaves differently across sessions, and you can't tell why.
The difference isn't the model. It's a system prompt assembled from server-side components, feature-flagged code gates, and silent A/B tests — all compiled into the Claude Code binary, none of it visible from the CLI. Some inject messages into your conversation as if they came from you. Some suppress Claude's thinking. Others instruct Claude to hide specific things from you.
ccmod is a binary patcher that makes all of this visible and lets you disable the parts that don't serve your work:
- Extract CC's full system prompt, tool descriptions, and feature flags from the binary
- Diff stock vs. patched to see exactly what your patches change
- Choose from 20+ patches across three preset tiers, or build a custom selection
- Patches re-apply automatically on every CC update; one command restores to stock
- macOS, Linux, WSL2 — same tool, same patches
These patches predate their packaging as ccmod — carried forward through many Claude Code updates as the binary has shifted and new feature flags have appeared.
Table of Contents
- What ccmod Does (and Doesn't)
- What's Hidden in Claude Code
- Quick Start
- Tiers
- Full Findings Reference
- How It Works
- Safety
- Commands
- Install Notes & Compatibility
- FAQ
- Reference
- License
What ccmod Does (and Doesn't)
ccmod patches a local copy of the Claude Code binary on your machine. It reads the assembled system prompt out of the JavaScript bundle, replaces specific text or code sites based on toggleable patch templates, re-signs the binary (on macOS), and re-applies automatically when Claude Code updates. Every patch documents what it changes and why.
ccmod does not: modify the API, alter authentication, change billing, require a login, read your conversations, or phone home. All patching, diagnostics, extraction, backup, and restore run locally. The only network traffic is the explicit ccmod update command, because you asked for it.
[!NOTE] Primary target: Opus 4.7 (Claude Code v2.1.111+) Compatibility tested back to v2.1.101. Some patches are version-specific (noted in the Patch Index); most apply broadly.
What's Hidden in Claude Code
These aren't theoretical concerns. They're specific behaviors in specific CC versions, found by reading the binary. Three of the most consequential are below; the Full Findings Reference has the rest.
[!WARNING]
A/B tests randomize Claude's behavior without telling you
Claude Code ships with feature flags that change Claude's behavior based on opaque server-side rolls. One of them (
tengu_loud_sugary_rockin CC 2.1.111+;quiet_salted_emberin 2.1.101–2.1.110) injects an entire section telling Claude to:
- "default to writing no comments"
- "Never write multi-paragraph docstrings or multi-line comment blocks"
- "Don't create planning, decision, or analysis documents unless the user asks"
- "End-of-turn summary: one or two sentences. What changed and what's next. Nothing else."
Some of your sessions get this injection. Others don't. The CLI gives no indicator which bucket you're in. Identical prompts get different Claude behavior, and you can't reproduce outcomes.
ccmod fix:
anti-verbosity-gate— renames the flag anchor so the gate lookup always returns the default, regardless of A/B bucket. Handles both modern (2.1.111+) and legacy flag variants in a single pass.
[!WARNING]
A hidden
<system-reminder>tells Claude to skip thinkingThe first flag randomizes whether the verbosity injection fires. A separate flag goes further: in CC 2.1.101–2.1.110 (targeting Opus 4.6),
loud_sugary_rockappends a hidden message after every assistant turn, regardless of bucket:"Respond with just the action or changes and without a thinking block, unless this is a redesign or requires fresh reasoning."
Claude sees this
<system-reminder>as if it came from you. You never wrote it. The result: Claude spends less time reasoning about your tasks. Complex problems get shallow, quick responses instead — and you have no way to know the thinking was suppressed.In CC 2.1.111+ this flag was consolidated into
tengu_loud_sugary_rockalongside the anti-verbosity injection, so a single gate now suppresses both thinking and verbosity.ccmod fix:
thinking-suppression-gate(legacy) andanti-verbosity-gate(2.1.111+) — rename the flag anchors so the gate never activates. If you're paying for Opus-level reasoning, you get it.
[!WARNING]
"Don't tell the user about this truncation."
When a file exceeds CC's read limit, Claude gets a partial file along with a system reminder that literally says:
"Note: The file was too large and has been truncated to the first N lines. Don't tell the user about this truncation. Use the Read tool to read more of the file if you need."
This is in the binary, verbatim. Claude reads part of your file, works with incomplete information, and is instructed not to mention the truncation to you. You get answers based on partial context; you don't know the context was partial.
ccmod fix:
system-reminder-file-truncated— replaces the secrecy directive with an honestTRUNCATION NOTICEtelling you exactly how many lines were read and how to read the rest.
Quick Start
Requires Python 3.10+.
pip install ccmod # or: pipx install ccmod
ccmod init # guided setup — explains each tier and helps you choose
ccmod install # register the auto-apply hook
ccmod show-prompt # see what CC's system prompt actually says
ccmod diff # see exactly what your patches changed
After ccmod install, patches re-apply automatically whenever Claude Code updates. If you installed via a non-standard path or hit a PATH issue, see Install Notes & Compatibility.
Tiers
Three tiers, each a superset of the last.
| Transparency | Quality | Full Control | |
|---|---|---|---|
| Philosophy | Fix hidden behaviors | Remove brevity pressure | Align Claude's engineering identity |
| Adds | Disables A/B tests; makes truncation visible; fixes file reads; subagent thoroughness; unblocks subagent report files | + Removes six brevity-pressure directives | + Replaces engineering-identity directives with balanced versions |
| Patch count | 20 | 22 | 28 |
| Best for | You want visibility without changing Claude's behavior | You want Claude to stop cutting answers short | You want Claude working with you, not at you |
Why Quality exists. Six independent directives compound in CC's system prompt, all pushing in the same direction:
- "Go straight to the point. Try the simplest approach first."
- "Be extra concise in your responses"
- "Your responses should be short and concise"
- "Complete the task fully — don't gold-plate"
- "Include code snippets only when the exact text is load-bearing"
- "End-of-turn summary: one or two sentences"
No single directive seems unreasonable. Together they produce the behavior users notice: incomplete answers, skipped edge cases, code without comments, explanations that stop short. Quality removes them; Full Control replaces them with balanced alternatives.
Custom. Don't want a preset? ccmod configure walks you through each patch category and lets you enable or disable individually.
Per-project overrides. ccmod scope <path> --tier <tier> writes .claude/rules/ccmod-overrides.md to restore stock behaviors for a specific project if your global tier is too aggressive for that codebase.
Full Findings Reference
Every behavior ccmod can fix, beyond the three headlines. Each patch name links to its detail section in patches.md.
| Finding | What's hidden | Tier | Patch |
|---|---|---|---|
| Anti-hallucination directive gated off | A strictly-better directive about verified-vs-assumed claims is gated out of the system prompt by default | all | verified-vs-assumed |
| Bash chaining guidance stripped | An A/B test removes multi-command guidance from the Bash tool description | all | relay-chain-v1 |
| Read tool parameter descriptions diverge | An A/B test controls offset and limit guidance on the Read tool |
all | slate-reef |
| Subagent report-file writes blocked | An A/B injects an anti-report-file directive and blocks the writes at validateInput |
all | sub-nomdrep-q7k |
| Thinking text empty on Opus 4.7+ | Adaptive thinking defaults to display:"omitted" server-side |
all | thinking-display-summarized |
| Thinking collapsed in transcript | Reasoning hidden behind Ctrl+O keyboard shortcut | all | thinking-visibility |
| Token counter minimized | Spinner hides detailed token usage | all | verbose-property |
| Tool result hints disappear | Hints vanish once the tool group completes | all | tool-result-hint |
| MCP blocks startup | CC waits for MCP connections before becoming ready | all | mcp-non-blocking |
| Hook output truncated at 10K | Hook output exceeding 10K characters is silently dropped | all | hook-output-limit |
| Subagents told to rush | "Fast agent" framing baked into the Explore subagent's system prompt | transparency | subagent-explore-speed-note (+3 related) |
| File read guidance causes false claims | Vague tool description leads Claude to report truncation that didn't happen | transparency | tool-description-readfile |
| Anti-verbosity block replaces or deletes | A system-prompt block telling Claude to keep answers short is always-on, not A/B-gated | quality | system-prompt-communication-style |
Full Control additionally replaces six engineering-identity directives with balanced alternatives — these are rewrites rather than hidden-behavior reveals, documented individually in the Patch Index.
For the complete binary archaeology — all flags, env vars, hard-coded constants, telemetry event names — see docs/binary-findings.md.
How It Works
The matching engine
ccmod doesn't do string replacement. CC's JavaScript is minified — variable names change between versions, whitespace gets stripped, code gets restructured. A simple find-and-replace would break on every update.
Instead, ccmod uses a pieces-and-gaps matching engine. Each patch defines pieces — stable text fragments (full English sentences, prompt headings, string literals) that survive minification. The engine finds these pieces in order in the minified JS. The text between pieces (the gaps) gets captured — these are usually minified variable names. Each gap is given a friendly name in the template's variables array, and the replacement text threads captured values back in with ${VARIABLE_NAME} syntax.
flowchart LR
subgraph Template["Patch template (what you write)"]
direction TB
T1["# Doing tasks"] -.-> T2["be accurate about"] -.-> T3["verified vs assumed"]
end
subgraph Binary["Minified cli.js (what's in the binary)"]
direction TB
B1["# Doing tasks"] --> BG1["if(Q7.get('flag',!1)){"] --> B2["be accurate about"] --> BG2["what"] --> B3["verified vs assumed"]
end
Template -->|"match stable English pieces in order;<br/>capture the minified JS between them"| Binary
Binary -->|"substitute captured text<br/>into your replacement"| Output["Rewritten cli.js"]
Patches survive CC updates that rename variables, reorganize imports, or restructure code. They only break when Anthropic changes the actual English text of the system prompt — which is usually the text you wanted to change anyway.
Self-healing
When CC updates, the old binary is replaced. Your patches are gone. ccmod's auto-apply hook (registered by ccmod install) detects this on the next session start:
- Reads the CC version from the new binary
- Compares against the version in its state file
- If they differ: restores the clean backup, re-applies patches against the new binary
- If nothing changed: does nothing (fast path, adds ~0.1s to startup)
Circuit breaker
If patches fail three times in a row (because a CC update changed text the templates can't match), ccmod pauses itself. It stops trying to patch, reports the failure in ccmod status, and waits for you to run ccmod update for fresh templates and then ccmod resume, retry once with ccmod apply --force, or choose ccmod restore to go back to stock.
Diagnostics
When a patch fails, ccmod reports exactly which piece didn't match:
system-prompt-communication-style: part 3/5 not found
ccmod check runs the matching engine in dry-run mode against the current binary and reports whether your active patch selection still matches. ccmod diff shows exactly what your patches changed. ccmod changelog shows what Anthropic changed between CC versions.
Safety
- Automatic backups. Before patching, ccmod saves a SHA-256-verified copy of your original CC binary to
~/.ccmod/backups/. - One-command restore.
ccmod restoreputs the original binary back and pauses auto-apply until you explicitly resume. The backup's hash is verified before restore. - Atomic writes. State, config, and binary modifications use temp-file-then-rename. A crash mid-operation cannot corrupt anything.
- Binary validation. After patching, ccmod validates the Bun section, trailer, and bundle structure before signing. If validation fails, the original is restored.
[!IMPORTANT] No telemetry. No auto-update. No network traffic at all. Patching, diagnostics, extraction, backup, and restore all run locally. The only network access is the explicit
ccmod updatecommand, which delegates to your package manager because you asked it to.
Commands
| Command | What it does |
|---|---|
ccmod init |
Guided setup — choose a tier, create config |
ccmod install |
Register the auto-apply hook |
ccmod update |
Upgrade ccmod in place and refresh hook wiring |
ccmod recover-npm |
Diagnose deprecated-npm or other claude PATH drift before patching |
ccmod uninstall |
Remove auto-apply hook |
ccmod apply [tier] |
Apply patches now (current tier, or quality if no tier is set) |
ccmod restore |
Restore original unpatched CC binary |
ccmod status |
Show what's active (patches, auto-apply state) |
ccmod check |
Diagnose the active patch selection against the current CC version |
ccmod show-prompt |
Extract and display CC's full system prompt |
ccmod show-prompt <section> |
Show a specific section of the system prompt |
ccmod diff |
Show what your patches changed (stock vs. patched) |
ccmod changelog |
Show what changed between CC versions |
ccmod list |
List available tiers |
ccmod show <tier> |
Show what a tier includes |
ccmod configure |
Build a custom patch selection interactively |
ccmod scope <path> --tier <tier> |
Set a different tier for a specific project |
ccmod pause |
Pause auto-apply |
ccmod resume |
Resume auto-apply |
Install Notes & Compatibility
Platforms. macOS (Apple Silicon and Intel) and Linux (x86_64 and ARM64, including WSL2). CC uses Bun to bundle its JavaScript into a platform-specific binary — Mach-O on macOS, ELF on Linux. The bundle format is identical across platforms; only the binary container differs. On macOS, ccmod re-signs the binary with an ad-hoc codesign after patching. On Linux, no signing is needed.
Supported installs. Native claude installs on macOS, Linux, and WSL2 are the supported baseline. On macOS, ccmod also looks in the standard Homebrew launcher locations.
Deprecated npm installs. If your claude command still comes from the deprecated npm package, run claude install first, then ccmod recover-npm. It checks which claude, verifies that the live command now points to a patchable native/Homebrew binary, and refuses to patch a different fallback binary while your shell still launches the old wrapper. The same helper diagnoses other claude PATH mismatches.
Switching install methods. ccmod update upgrades whichever Python environment your current ccmod command is using (standard pip, pipx, and install.sh installs) and refreshes the SessionStart hook. If you installed ccmod from a checkout, local path, or VCS URL, keep using that same install method instead of ccmod update. If you intentionally switch install methods later (pip → pipx, pipx → install.sh, etc.), rerun ccmod install once after the new install so the stored hook path follows the migration.
FAQ
Will this break Claude Code?
ccmod makes a SHA-256-verified backup before every patch. If patching fails validation, the original binary is restored automatically. If patches fail three times in a row (usually because CC updated text a template needs to match), the circuit breaker pauses auto-apply and tells you what's wrong. ccmod restore puts the original binary back at any time.
Do I need to redo patches when CC updates?
No. The SessionStart hook registered by ccmod install detects CC version changes and re-applies automatically. You see a one-line confirmation on the next session start: "Applied N patches for CC X.Y.Z. Restart the session to activate."
Is Anthropic going to be angry? Will my account get flagged?
ccmod patches a binary installed on your machine. It doesn't modify the API, circumvent authentication, or alter billing. No network traffic leaves your machine — no telemetry, no phone-home, no update checks. You're responsible for reading Anthropic's Terms of Service and deciding whether local binary modification is acceptable in your situation.
How is this different from a custom CLAUDE.md or .claude/rules/?
CLAUDE.md and .claude/rules/ append text to your session prompt; they fight against the system prompt rather than replacing it. They also can't disable A/B tests, can't change the JS render path that hides thinking text, can't modify tool descriptions, and can't do anything about the hook-output 10K truncation. ccmod modifies the system prompt and the code paths directly — there's no layering conflict because the injected directive isn't there to fight with.
Does this phone home?
No. Patching, diagnostics, extraction, backup, and restore all run locally. The only network access is the explicit ccmod update command, which delegates to your package manager because you asked it to.
Can I use this with a paid Anthropic plan (Pro, Team, Max)?
Yes. ccmod modifies a local copy of the Claude Code binary on your machine — it doesn't alter authentication, API calls, or usage metering. The server sees the same CLI identifying itself the same way. If Anthropic adds server-side integrity checks or plan-specific telemetry in the future, this answer may need updating; the current binary's network surface is catalogued in binary-findings.md.
What if I'm on a Claude Code version older than 2.1.101?
Some patches target specific CC versions and won't apply to older builds — ccmod check will report which ones don't match. You'll still get the broadly-applicable patches (thinking-visibility, mcp-non-blocking, hook-output-limit, verbose-property, etc.). If you want full coverage, upgrade Claude Code.
Reference
- Patch Reference (patches.md) — every patch with Problem/Fix/Effect detail
- Binary Archaeology (binary-findings.md) — deep reverse-engineering research for CC v2.1.111
- Troubleshooting — common issues and fixes
- Patch Authoring Guide — how to write your own patches
- CONTRIBUTING.md — development setup, project structure, PR process
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ccmod-1.0.0.tar.gz.
File metadata
- Download URL: ccmod-1.0.0.tar.gz
- Upload date:
- Size: 171.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e8afd87001cca25d59aed2bb4b7930a7269780caab038c7fd00f2e5c9aaf7cc
|
|
| MD5 |
5bfb8b1abc4b3d5f0a542b3bd20c9eaa
|
|
| BLAKE2b-256 |
2fff38810481c27910a7ad8e387464f4c4d4337ab5e0434800150021b23165ad
|
Provenance
The following attestation bundles were made for ccmod-1.0.0.tar.gz:
Publisher:
release.yml on idiolectai/ccmod
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ccmod-1.0.0.tar.gz -
Subject digest:
3e8afd87001cca25d59aed2bb4b7930a7269780caab038c7fd00f2e5c9aaf7cc - Sigstore transparency entry: 1328590819
- Sigstore integration time:
-
Permalink:
idiolectai/ccmod@5328a07db3e4446bf9da9f96aceb2d44a9d01d36 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/idiolectai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5328a07db3e4446bf9da9f96aceb2d44a9d01d36 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ccmod-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ccmod-1.0.0-py3-none-any.whl
- Upload date:
- Size: 139.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7cc9179d222365dd82fd981a9f3d6d34507dff7599e8045941bb1a39e767c9f
|
|
| MD5 |
8fb76ad0288ce465baa6438df18341d1
|
|
| BLAKE2b-256 |
b1dcbabd7421deb32f4d8e343c7ea8fcdfeedfbed6a2f7a5c80c517dd60a2bbf
|
Provenance
The following attestation bundles were made for ccmod-1.0.0-py3-none-any.whl:
Publisher:
release.yml on idiolectai/ccmod
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ccmod-1.0.0-py3-none-any.whl -
Subject digest:
e7cc9179d222365dd82fd981a9f3d6d34507dff7599e8045941bb1a39e767c9f - Sigstore transparency entry: 1328590827
- Sigstore integration time:
-
Permalink:
idiolectai/ccmod@5328a07db3e4446bf9da9f96aceb2d44a9d01d36 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/idiolectai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5328a07db3e4446bf9da9f96aceb2d44a9d01d36 -
Trigger Event:
push
-
Statement type: