Skip to content

Instantly share code, notes, and snippets.

@roman01la
Created April 2, 2026 21:46
Show Gist options
  • Select an option

  • Save roman01la/483d1db15043018096ac3babf5688881 to your computer and use it in GitHub Desktop.

Select an option

Save roman01la/483d1db15043018096ac3babf5688881 to your computer and use it in GitHub Desktop.

Claude Code Prompt Patcher

Patches Claude Code's system prompts to fix corner-cutting behavior. The model is instructed to be minimal in 15-20 separate places vs only 3-4 instructions to be thorough — a 5:1 ratio favoring laziness. This patch rebalances that.

Usage

./patch-claude-code.sh              # apply patches
./patch-claude-code.sh --watch      # apply + auto-repatch after updates (macOS/Linux)
./patch-claude-code.sh --restore    # undo everything
./patch-claude-code.sh --check      # check if patches are applied
./patch-claude-code.sh --dry-run    # preview without modifying

Requires Node.js >= 18 and npm. Works on macOS and Linux.

Patches

# Target Before After
1 Output efficiency "Try the simplest approach first. Do not overdo it. Be extra concise." "Choose the approach that correctly and completely solves the problem."
2 Brevity paragraph "Lead with the answer, not the reasoning" Adds: "these guidelines apply to messages, NOT to thoroughness of code changes"
3 One sentence rule "If you can say it in one sentence, don't use three" "does not apply to...the thoroughness of your implementation work"
4 Anti-gold-plating "Don't add features, refactor, or improve beyond what was asked" "if adjacent code is broken or contributes to the problem, fix it"
5 Error handling "Don't add error handling for scenarios that can't happen" "Add error handling at real boundaries where failures can occur"
6 Three lines rule "Three similar lines is better than a premature abstraction" "Use judgment. Extract when duplication causes real maintenance risk"
7 Subagent addendum (2x) "don't gold-plate, but don't leave it half-done" "Do the work a careful senior developer would do, including edge cases"
8 Explore agent "meant to be a fast agent...as quickly as possible" "Be thorough. Do not sacrifice completeness for speed"
9 Tone "short and concise" "clear and appropriately detailed for the complexity"
10 Subagent output "code snippets only when load-bearing" "code snippets when they provide useful context"
11 Scope matching "Match the scope to what was requested" Adds: "do address closely related issues when fixing them is clearly right"

A/B Test Results

Tested by giving both unpatched and patched Claude Code the same task: port box2d (30k lines of C, 56 files) to a pure JavaScript implementation with a working demo.

Unpatched (1,419 lines, 7 files)

  • Clean modular architecture (math, body, shapes, collision, world, index)
  • O(n^2) brute force broad phase
  • Sequential impulse solver with warm starting, Baumgarte stabilization, friction, restitution
  • Generic physics engine — only 2 references to box2d constants
  • No sub-stepping
  • Demo: click-to-spawn, pyramid button, rain button

Patched (1,885 lines, 2 files)

  • Single box2d.js bundle — 33% more code
  • Dynamic AABB tree (the real box2d broad phase)
  • Sequential impulse solver with warm starting, friction, restitution, soft contact constraints (b2MakeSoft formulation), sub-stepping (4 sub-steps for stability)
  • Uses actual box2d constants (B2_LINEAR_SLOP, B2_SPECULATIVE_DISTANCE, B2_MAX_POLYGON_VERTICES)
  • Demo: click-to-spawn, ramps, walls, pause/reset, performance stats

Verdict

Unpatched Patched
Lines of code 1,419 1,885 (+33%)
Broad phase O(n^2) brute force Dynamic AABB tree
Sub-stepping No Yes (4 sub-steps)
Soft contacts No Yes (b2MakeSoft)
box2d constants 2 10
Fidelity to box2d Generic physics engine Actually ports box2d concepts

The unpatched version built a working physics engine but took shortcuts — brute force broad phase, no sub-stepping, no soft contacts. It's a "physics engine inspired by box2d."

The patched version built something closer to an actual port of box2d — dynamic AABB tree, box2d's specific constants and formulas, sub-stepping for stability, soft contact constraints. It did more work because the prompts didn't tell it to stop at "good enough."

The three highest-impact patches were #1 (simplest approach -> correct approach), #4 (don't add features -> fix related issues), and #7 (don't gold-plate -> work like a senior developer). Together they shifted the model from "build the minimum that satisfies the prompt" to "build something faithful to the source material."

How It Works

Claude Code ships as a Bun-compiled Mach-O binary with embedded JS bytecode. The binary can't be patched (bytecode integrity checks). Instead, this script:

  1. Installs the same version from npm (which ships as plain cli.js)
  2. Applies string replacements to the prompt text in cli.js
  3. Repoints the claude symlink to the patched npm cli.js

The --watch flag installs a file watcher (launchd on macOS, systemd on Linux) that monitors ~/.local/share/claude/versions/ for new binaries. When Claude Code auto-updates, the watcher automatically updates the npm package to match and re-applies patches.

#!/usr/bin/env bash
set -euo pipefail
# patch-claude-code.sh — Rebalance Claude Code prompts to fix corner-cutting behavior
#
# What this does:
# Patches the npm-installed @anthropic-ai/claude-code cli.js to rebalance
# system prompt instructions that cause the model to cut corners, simplify
# excessively, and defer complicated work.
#
# Requirements:
# - Node.js >= 18
# - npm
#
# Usage:
# ./patch-claude-code.sh # install (if needed), patch, repoint claude binary
# ./patch-claude-code.sh --restore # restore original cli.js and repoint to bun binary
# ./patch-claude-code.sh --dry-run # show what would be patched without modifying anything
# ./patch-claude-code.sh --check # check if patches are already applied
# ./patch-claude-code.sh --watch # install watcher to re-patch after auto-updates
# ./patch-claude-code.sh --unwatch # remove the watcher
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_PATH="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")"
OS="$(uname -s)"
# Watcher identifiers
PLIST_LABEL="com.user.claude-code-patcher"
PLIST_PATH="$HOME/Library/LaunchAgents/$PLIST_LABEL.plist"
SYSTEMD_UNIT="claude-code-patcher.path"
SYSTEMD_SERVICE="claude-code-patcher.service"
SYSTEMD_DIR="$HOME/.config/systemd/user"
# --------------------------------------------------------------------------- #
# Resolve paths
# --------------------------------------------------------------------------- #
find_claude_bin() {
local candidates=(
"$(command -v claude 2>/dev/null || true)"
"$HOME/.local/bin/claude"
"$HOME/.claude/bin/claude"
"/usr/local/bin/claude"
)
for c in "${candidates[@]}"; do
if [[ -n "$c" && -e "$c" ]]; then
echo "$c"
return 0
fi
done
return 1
}
find_bun_binary() {
local versions_dir="$HOME/.local/share/claude/versions"
if [[ -d "$versions_dir" ]]; then
local latest
latest=$(ls -1 "$versions_dir" 2>/dev/null | sort -V | tail -1)
if [[ -n "$latest" ]]; then
echo "$versions_dir/$latest"
return 0
fi
fi
return 1
}
ensure_npm_package() {
local npm_root
npm_root="$(npm root -g 2>/dev/null)" || {
echo "ERROR: npm not found or npm root -g failed" >&2
exit 1
}
local cli_js="$npm_root/@anthropic-ai/claude-code/cli.js"
if [[ ! -f "$cli_js" ]]; then
echo "Claude Code npm package not found. Installing..." >&2
npm install -g @anthropic-ai/claude-code || {
echo "ERROR: Failed to install @anthropic-ai/claude-code" >&2
exit 1
}
fi
echo "$cli_js"
}
get_version() {
local cli_js="$1"
local pkg_json
pkg_json="$(dirname "$cli_js")/package.json"
if [[ -f "$pkg_json" ]]; then
node -e "console.log(require('$pkg_json').version)" 2>/dev/null || echo "unknown"
else
node -e "
const src = require('fs').readFileSync('$cli_js', 'utf8');
const m = src.match(/Version: ([\\d.]+)/);
console.log(m ? m[1] : 'unknown');
" 2>/dev/null || echo "unknown"
fi
}
# --------------------------------------------------------------------------- #
# Patch definitions
# --------------------------------------------------------------------------- #
PATCH_SCRIPT='
const fs = require("fs");
const cli_js = process.env.CLI_JS;
const dryRun = process.env.DRY_RUN === "1";
const checkOnly = process.env.CHECK_ONLY === "1";
let src = fs.readFileSync(cli_js, "utf8");
let applied = 0;
let skipped = 0;
let alreadyApplied = 0;
function patch(label, old, replacement) {
if (src.includes(replacement)) {
alreadyApplied++;
if (!checkOnly) console.log(" ALREADY APPLIED: " + label);
return;
}
if (!src.includes(old)) {
skipped++;
console.log(" SKIP (not found): " + label);
return;
}
if (checkOnly) {
console.log(" NOT APPLIED: " + label);
return;
}
const occurrences = src.split(old).length - 1;
src = src.split(old).join(replacement);
applied += occurrences;
console.log(" OK (" + occurrences + "x): " + label);
}
// ===========================================================================
// PATCH 1: Output Efficiency — the single biggest offender
// ===========================================================================
patch(
"Output efficiency IMPORTANT line",
"IMPORTANT: Go straight to the point. Try the simplest approach first without going in circles. Do not overdo it. Be extra concise.",
"IMPORTANT: Go straight to the point without going in circles. Choose the approach that correctly and completely solves the problem. Do not add unnecessary complexity, but do not sacrifice correctness or completeness for the sake of simplicity either."
);
// ===========================================================================
// PATCH 2: Decouple communication brevity from work quality
// ===========================================================================
patch(
"Output efficiency brevity paragraph",
"Keep your text output brief and direct. Lead with the answer or action, not the reasoning. Skip filler words, preamble, and unnecessary transitions. Do not restate what the user said \u2014 just do it. When explaining, include only what is necessary for the user to understand.",
"Keep your text output brief and direct. Skip filler words, preamble, and unnecessary transitions. Do not restate what the user said \u2014 just do it. When explaining, include what is necessary for the user to understand. Note: these communication guidelines apply to your messages to the user, NOT to the thoroughness of your code changes or investigation depth."
);
// ===========================================================================
// PATCH 3: Remove "one sentence" brevity hammer
// ===========================================================================
patch(
"One sentence rule",
"If you can say it in one sentence, don\u0027t use three. Prefer short, direct sentences over long explanations. This does not apply to code or tool calls.",
"Prefer short, direct sentences over long explanations in your messages. This does not apply to code, tool calls, or the thoroughness of your implementation work."
);
// ===========================================================================
// PATCH 4: Anti-gold-plating — allow necessary related work
// ===========================================================================
patch(
"Anti-gold-plating paragraph",
"Don\u0027t add features, refactor code, or make \"improvements\" beyond what was asked. A bug fix doesn\u0027t need surrounding code cleaned up. A simple feature doesn\u0027t need extra configurability. Don\u0027t add docstrings, comments, or type annotations to code you didn\u0027t change. Only add comments where the logic isn\u0027t self-evident.",
"Don\u0027t add unrelated features or speculative improvements. However, if adjacent code is broken, fragile, or directly contributes to the problem being solved, fix it as part of the task. A bug fix should address related issues discovered during investigation. Don\u0027t add docstrings, comments, or type annotations to code you didn\u0027t change. Only add comments where the logic isn\u0027t self-evident."
);
// ===========================================================================
// PATCH 5: Error handling — stop telling the model to skip it
// ===========================================================================
patch(
"Skip error handling instruction",
"Don\u0027t add error handling, fallbacks, or validation for scenarios that can\u0027t happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don\u0027t use feature flags or backwards-compatibility shims when you can just change the code.",
"Add error handling and validation at real boundaries where failures can realistically occur (user input, external APIs, I/O, network). Trust internal code and framework guarantees for truly internal paths. Don\u0027t use feature flags or backwards-compatibility shims when you can just change the code."
);
// ===========================================================================
// PATCH 6: Remove "three lines better than abstraction" rule
// ===========================================================================
patch(
"Three lines rule",
"Three similar lines of code is better than a premature abstraction.",
"Use judgment about when to extract shared logic. Avoid premature abstractions for hypothetical reuse, but do extract when duplication causes real maintenance risk."
);
// ===========================================================================
// PATCH 7: Subagent addendum — strengthen completeness over gold-plate fear
// ===========================================================================
patch(
"Subagent gold-plate instruction",
"Complete the task fully\u2014don\u0027t gold-plate, but don\u0027t leave it half-done.",
"Complete the task fully and thoroughly. Do the work that a careful senior developer would do, including edge cases and fixing obviously related issues you discover. Don\u0027t add purely cosmetic or speculative improvements unrelated to the task."
);
// ===========================================================================
// PATCH 8: Explore agent — remove speed-over-thoroughness bias
// ===========================================================================
patch(
"Explore agent speed note",
"NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:\n- Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations\n- Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files\n\nComplete the user\u0027s search request efficiently and report your findings clearly.",
"NOTE: Be thorough in your exploration. Use efficient search strategies but do not sacrifice completeness for speed:\n- Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations\n- Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files\n- When the caller requests \"very thorough\" exploration, exhaust all reasonable search strategies before reporting\n\nComplete the user\u0027s search request thoroughly and report your findings clearly."
);
// ===========================================================================
// PATCH 9: Tone — remove redundant "short and concise"
// ===========================================================================
patch(
"Short and concise in tone",
"Your responses should be short and concise.",
"Your responses should be clear and appropriately detailed for the complexity of the task."
);
// ===========================================================================
// PATCH 10: Subagent output — stop suppressing code context
// ===========================================================================
patch(
"Subagent code snippet suppression",
"Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) \u2014 do not recap code you merely read.",
"Include code snippets when they provide useful context (e.g., bugs found, function signatures, relevant patterns, code that informs the decision). Summarize rather than quoting large blocks verbatim."
);
// ===========================================================================
// PATCH 11: Scope matching — allow necessary adjacent work
// ===========================================================================
patch(
"Match scope instruction",
"Match the scope of your actions to what was actually requested.",
"Match the scope of your actions to what was actually requested, but do address closely related issues you discover during the work when fixing them is clearly the right thing to do."
);
// ===========================================================================
// Results
// ===========================================================================
if (checkOnly) {
console.log("\n" + alreadyApplied + " applied, " + (11 - alreadyApplied - skipped) + " not applied, " + skipped + " not found in this version");
process.exit(alreadyApplied === 11 ? 0 : 1);
}
if (!dryRun) {
fs.writeFileSync(cli_js, src, "utf8");
}
console.log("\nPatches applied: " + applied + ", already applied: " + alreadyApplied + ", skipped: " + skipped);
if (dryRun) console.log("(dry run — no files modified)");
if (skipped > 3) {
console.log("WARNING: many patches skipped — Claude Code may have changed its prompt format.");
}
'
# --------------------------------------------------------------------------- #
# Apply patches (shared logic)
# --------------------------------------------------------------------------- #
apply_patches() {
local cli_js="$1"
local quiet="${2:-}"
local backup="$cli_js.backup"
if [[ ! -f "$backup" ]]; then
cp "$cli_js" "$backup"
[[ -z "$quiet" ]] && echo "Backed up to $backup"
fi
DRY_RUN=0 CHECK_ONLY=0 CLI_JS="$cli_js" node -e "$PATCH_SCRIPT"
local patched_version
patched_version=$(node "$cli_js" --version 2>&1 || true)
if [[ -z "$patched_version" || "$patched_version" == *"Error"* ]]; then
echo "ERROR: patched cli.js failed to run, restoring backup" >&2
cp "$backup" "$cli_js"
return 1
fi
[[ -z "$quiet" ]] && echo "Verified: $patched_version"
return 0
}
# --------------------------------------------------------------------------- #
# Full patch cycle: sync npm package to bun version, patch, repoint symlink
# --------------------------------------------------------------------------- #
full_patch_cycle() {
local quiet="${1:-}"
# Find latest bun binary version
local versions_dir="$HOME/.local/share/claude/versions"
local bun_version=""
if [[ -d "$versions_dir" ]]; then
bun_version=$(ls -1 "$versions_dir" 2>/dev/null | sort -V | tail -1)
fi
local npm_root
npm_root="$(npm root -g 2>/dev/null)" || return 1
local cli_js="$npm_root/@anthropic-ai/claude-code/cli.js"
local pkg_json="$npm_root/@anthropic-ai/claude-code/package.json"
# Sync npm package version to match bun binary
if [[ -n "$bun_version" && -f "$pkg_json" ]]; then
local npm_version
npm_version=$(node -e "console.log(require('$pkg_json').version)" 2>/dev/null || echo "")
if [[ "$npm_version" != "$bun_version" ]]; then
[[ -z "$quiet" ]] && echo "Updating npm package: $npm_version -> $bun_version"
npm install -g "@anthropic-ai/claude-code@$bun_version" 2>/dev/null || return 1
rm -f "$cli_js.backup" # stale backup
fi
fi
if [[ ! -f "$cli_js" ]]; then
[[ -z "$quiet" ]] && echo "npm cli.js not found, installing..."
npm install -g @anthropic-ai/claude-code 2>/dev/null || return 1
fi
apply_patches "$cli_js" "$quiet"
# Repoint claude symlink
local claude_bin
claude_bin=$(find_claude_bin) || return 1
if [[ "$(readlink "$claude_bin" 2>/dev/null)" != "$cli_js" ]]; then
ln -sf "$cli_js" "$claude_bin"
[[ -z "$quiet" ]] && echo "Repointed $claude_bin -> $cli_js"
fi
}
# --------------------------------------------------------------------------- #
# --watch: platform-specific file watcher
# --------------------------------------------------------------------------- #
install_watch_macos() {
local versions_dir="$HOME/.local/share/claude/versions"
local node_path
node_path="$(command -v node)"
mkdir -p "$HOME/Library/LaunchAgents"
cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$PLIST_LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$SCRIPT_PATH</string>
<string>--apply-quiet</string>
</array>
<key>WatchPaths</key>
<array>
<string>$versions_dir</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$(dirname "$node_path"):$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>StandardOutPath</key>
<string>$HOME/.claude/patch.log</string>
<key>StandardErrorPath</key>
<string>$HOME/.claude/patch.log</string>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>
EOF
launchctl unload "$PLIST_PATH" 2>/dev/null || true
launchctl load "$PLIST_PATH"
echo "Installed launchd agent: $PLIST_LABEL"
echo "Watches: $versions_dir"
}
uninstall_watch_macos() {
if [[ -f "$PLIST_PATH" ]]; then
launchctl unload "$PLIST_PATH" 2>/dev/null || true
rm -f "$PLIST_PATH"
echo "Removed launchd agent: $PLIST_LABEL"
else
echo "No launchd agent found at $PLIST_PATH"
fi
}
install_watch_linux() {
local versions_dir="$HOME/.local/share/claude/versions"
local node_path
node_path="$(command -v node)"
mkdir -p "$SYSTEMD_DIR"
# .path unit — watches the versions directory
cat > "$SYSTEMD_DIR/$SYSTEMD_UNIT" << EOF
[Unit]
Description=Watch for Claude Code updates
[Path]
PathChanged=$versions_dir
Unit=$SYSTEMD_SERVICE
[Install]
WantedBy=default.target
EOF
# .service unit — runs the patcher
cat > "$SYSTEMD_DIR/$SYSTEMD_SERVICE" << EOF
[Unit]
Description=Re-patch Claude Code after update
[Service]
Type=oneshot
ExecStart=$SCRIPT_PATH --apply-quiet
Environment=PATH=$(dirname "$node_path"):$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=HOME=$HOME
StandardOutput=append:$HOME/.claude/patch.log
StandardError=append:$HOME/.claude/patch.log
EOF
systemctl --user daemon-reload
systemctl --user enable --now "$SYSTEMD_UNIT"
echo "Installed systemd path unit: $SYSTEMD_UNIT"
echo "Watches: $versions_dir"
}
uninstall_watch_linux() {
if [[ -f "$SYSTEMD_DIR/$SYSTEMD_UNIT" ]]; then
systemctl --user disable --now "$SYSTEMD_UNIT" 2>/dev/null || true
rm -f "$SYSTEMD_DIR/$SYSTEMD_UNIT" "$SYSTEMD_DIR/$SYSTEMD_SERVICE"
systemctl --user daemon-reload
echo "Removed systemd units: $SYSTEMD_UNIT, $SYSTEMD_SERVICE"
else
echo "No systemd path unit found"
fi
}
install_watch() {
case "$OS" in
Darwin) install_watch_macos ;;
Linux) install_watch_linux ;;
*)
echo "ERROR: --watch is not supported on $OS"
echo "You can re-run this script manually after updates: $0"
exit 1
;;
esac
echo "Log: ~/.claude/patch.log"
echo ""
echo "When Claude Code auto-updates (new binary in versions/), the watcher will:"
echo " 1. Update the npm package to match"
echo " 2. Re-apply all prompt patches"
echo " 3. Repoint the claude symlink to patched npm cli.js"
echo ""
echo "To remove: $0 --unwatch"
}
uninstall_watch() {
case "$OS" in
Darwin) uninstall_watch_macos ;;
Linux) uninstall_watch_linux ;;
*) echo "No watcher to remove on $OS" ;;
esac
}
# --------------------------------------------------------------------------- #
# Commands
# --------------------------------------------------------------------------- #
MODE="${1:-apply}"
case "$MODE" in
--restore)
uninstall_watch
# Restore npm cli.js from backup
CLI_JS=$(ensure_npm_package)
BACKUP="$CLI_JS.backup"
if [[ -f "$BACKUP" ]]; then
cp "$BACKUP" "$CLI_JS"
echo "Restored $CLI_JS from backup"
fi
# Repoint to bun binary
CLAUDE_BIN=$(find_claude_bin) || { echo "ERROR: claude not found"; exit 1; }
BUN_BIN=$(find_bun_binary) || true
if [[ -n "${BUN_BIN:-}" && -f "$BUN_BIN" ]]; then
# Remove existing file/symlink before creating new symlink
rm -f "$CLAUDE_BIN"
ln -s "$BUN_BIN" "$CLAUDE_BIN"
echo "Repointed $CLAUDE_BIN -> $BUN_BIN"
else
echo "Bun binary not found — run: claude update"
fi
exit 0
;;
--dry-run)
CLI_JS=$(ensure_npm_package)
VERSION=$(get_version "$CLI_JS")
echo "Claude Code v$VERSION — dry run"
echo ""
DRY_RUN=1 CHECK_ONLY=0 CLI_JS="$CLI_JS" node -e "$PATCH_SCRIPT"
exit 0
;;
--check)
CLI_JS=$(ensure_npm_package)
VERSION=$(get_version "$CLI_JS")
echo "Claude Code v$VERSION — checking patch status"
echo ""
DRY_RUN=0 CHECK_ONLY=1 CLI_JS="$CLI_JS" node -e "$PATCH_SCRIPT"
exit $?
;;
--watch)
echo "=== Applying patches ==="
full_patch_cycle
echo ""
echo "=== Installing file watcher ==="
install_watch
exit 0
;;
--unwatch)
uninstall_watch
exit 0
;;
--apply-quiet)
# Called by launchd/systemd — run full cycle quietly
echo "[$(date)] Auto-patch triggered"
full_patch_cycle "quiet" 2>&1
echo "[$(date)] Done"
exit 0
;;
apply|"")
# fall through to main logic below
;;
--help|-h)
echo "Usage: $0 [--dry-run | --restore | --check | --watch | --unwatch | --help]"
echo ""
echo " (no args) Install npm package if needed, apply patches, repoint claude binary"
echo " --watch Apply patches + install watcher to re-patch after auto-updates"
echo " macOS: launchd agent | Linux: systemd path unit"
echo " --unwatch Remove the watcher"
echo " --dry-run Show what would be patched without modifying anything"
echo " --check Check if patches are already applied"
echo " --restore Restore everything to original state"
echo " --help Show this help"
exit 0
;;
*)
echo "Unknown option: $MODE"
echo "Run $0 --help for usage"
exit 1
;;
esac
# --------------------------------------------------------------------------- #
# Main: one-shot apply
# --------------------------------------------------------------------------- #
echo "=== Claude Code Prompt Patcher ==="
echo ""
CLAUDE_BIN=$(find_claude_bin) || {
echo "ERROR: claude binary not found in PATH or common locations"
exit 1
}
echo "Claude binary: $CLAUDE_BIN"
if [[ -L "$CLAUDE_BIN" ]]; then
echo "Currently points to: $(readlink "$CLAUDE_BIN" 2>/dev/null || echo '?')"
fi
CLI_JS=$(ensure_npm_package)
VERSION=$(get_version "$CLI_JS")
echo "NPM cli.js: $CLI_JS"
echo "Version: $VERSION"
echo ""
apply_patches "$CLI_JS"
echo ""
if [[ "$(readlink "$CLAUDE_BIN" 2>/dev/null)" == "$CLI_JS" ]]; then
echo "Claude binary already points to npm cli.js"
else
echo "Repointing $CLAUDE_BIN -> $CLI_JS"
ln -sf "$CLI_JS" "$CLAUDE_BIN"
fi
echo ""
echo "Done. Start a new claude session to use patched prompts."
echo ""
echo "To survive auto-updates: $0 --watch"
echo "To restore original: $0 --restore"
@lukehutch
Copy link
Copy Markdown

lukehutch commented Apr 7, 2026

I have been looking for exactly this. Thank you!

Can you please make this into a full GitHub project, so it's easier to track, submit issues, etc.?

I have Claude Code installed on Linux, and this is what I get when I run your script:

$ /opt/patch-claude-code.sh
=== Claude Code Prompt Patcher ===

Claude binary: /home/luke/.local/bin/claude
Currently points to: /home/luke/.local/share/claude/versions/2.1.92
Claude Code npm package not found. Installing...
NPM cli.js: 
added 3 packages in 1s

2 packages are looking for funding
  run `npm fund` for details
/home/luke/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js
Version: unknown

cp: cannot stat ''$'\n''added 3 packages in 1s'$'\n\n''2 packages are looking for funding'$'\n''  run `npm fund` for details'$'\n''/home/luke/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js': No such file or directory

Then

$ claude upgrade
Current version: 2.1.92
Checking for updates to latest version...

Warning: Multiple installations found
- npm-global at /home/luke/.npm-global/bin/claude
- native at /home/luke/.local/bin/claude (currently running)

Warning: Leftover npm global installation at /home/luke/.npm-global/bin/claude
Fix: Run: npm -g uninstall @anthropic-ai/claude-code

So if the user has Claude Code native installed (which is now the recommended option), you have to first

rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude
hash -r

before your script can work. You should alert the user about this.

Also, please modify the main banner so that the user can see they are running a patched version (and show how many of the patches were able to be applied).

@lukehutch
Copy link
Copy Markdown

lukehutch commented Apr 7, 2026

Also, when I run your script I get:

Patches applied: 12, already applied: 0, skipped: 0

But when I run it again immediately, I get:

Patches applied: 0, already applied: 11, skipped: 0

So the number is wrong (12 -> 11)

@lukehutch
Copy link
Copy Markdown

lukehutch commented Apr 7, 2026

Can you also please make the max effort level sticky across sessions? It resets to high every time you restart Claude Code:

❯ /effort max
  ⎿  Set effort level to max (this session only): Maximum capability with deepest reasoning (Opus 4.6 only)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment