Skip to content

Instantly share code, notes, and snippets.

@yuxi-liu-wired
Last active March 10, 2026 05:45
Show Gist options
  • Select an option

  • Save yuxi-liu-wired/c46cc9e25779968ca3227e3d270aaa0e to your computer and use it in GitHub Desktop.

Select an option

Save yuxi-liu-wired/c46cc9e25779968ca3227e3d270aaa0e to your computer and use it in GitHub Desktop.
Erase the ASCII art in Claude Code
#!/usr/bin/env python3
"""Patch Claude Code binary to blank all ASCII art (hat, Clawd, welcome screen).
Art characters are stored as JS unicode escapes like \u2591 or \u259F
(6 ASCII bytes each, mixed case hex). We replace them with \u200B
(zero-width space), also 6 bytes, so art vanishes without corrupting
the binary.
Works even while Claude Code is running via atomic rename.
Usually called by the wrapper at ~/.pixi/bin/claude with --single.
Usage:
python3 erase_ascii_claude_code.py # patch all binaries + stamp
python3 erase_ascii_claude_code.py --single <path> # patch one binary + stamp
python3 erase_ascii_claude_code.py --restore # restore current binary from backup
"""
import os
import sys
CLAUDE_LINK = os.path.expanduser("~/.local/bin/claude")
VERSIONS_DIR = os.path.expanduser("~/.local/share/claude/versions")
# All block element codepoints used in the art
ART_CODEPOINTS = [
'2580', # ▀ UPPER HALF BLOCK
'2584', # ▄ LOWER HALF BLOCK
'2588', # █ FULL BLOCK
'258C', # ▌ LEFT HALF BLOCK
'2590', # ▐ RIGHT HALF BLOCK
'2591', # ░ LIGHT SHADE
'2592', # ▒ MEDIUM SHADE
'2593', # ▓ DARK SHADE
'2596', # ▖ QUADRANT LOWER LEFT
'2597', # ▗ QUADRANT LOWER RIGHT
'2598', # ▘ QUADRANT UPPER LEFT
'2599', # ▙ QUADRANT UL+LL+LR
'259B', # ▛ QUADRANT UL+UR+LL
'259C', # ▜ QUADRANT UL+UR+LR
'259D', # ▝ QUADRANT UPPER RIGHT
'259F', # ▟ QUADRANT UR+LL+LR
'2026', # … HORIZONTAL ELLIPSIS (art separator lines)
'273B', # ✻ TEARDROP-SPOKED ASTERISK
]
ZWSP_6 = b'\\u200B' # zero-width space escape, same 6 bytes
def resolve_binary():
"""Follow the claude symlink to find the actual binary."""
if not os.path.islink(CLAUDE_LINK):
sys.exit(f"ERROR: {CLAUDE_LINK} is not a symlink. Can't resolve binary path.")
target = os.path.realpath(CLAUDE_LINK)
if not os.path.isfile(target):
sys.exit(f"ERROR: Resolved path {target} does not exist.")
return target
def find_all_binaries():
"""Find all executable binaries in the versions directory."""
if not os.path.isdir(VERSIONS_DIR):
sys.exit(f"ERROR: {VERSIONS_DIR} does not exist.")
binaries = []
for entry in os.listdir(VERSIONS_DIR):
path = os.path.join(VERSIONS_DIR, entry)
if os.path.isfile(path) and os.access(path, os.X_OK):
binaries.append(path)
return sorted(binaries)
def wait_for_stable(path, interval=5, rounds=3):
"""Wait until a file's size and mtime stop changing.
Checks every `interval` seconds; needs `rounds` consecutive
identical readings before returning. Gives up after 2 minutes.
"""
import time
deadline = time.monotonic() + 120
prev = None
stable = 0
while time.monotonic() < deadline:
try:
st = os.stat(path)
cur = (st.st_size, st.st_mtime)
except OSError:
cur = None
if cur == prev:
stable += 1
if stable >= rounds:
return
else:
stable = 0
prev = cur
time.sleep(interval)
print(f"WARNING: {path} still changing after 2 min, patching anyway.")
def build_escape_list():
"""Build list of both upper and lowercase \\uXXXX escapes."""
seen = set()
for cp in ART_CODEPOINTS:
seen.add(b'\\u' + cp.upper().encode())
seen.add(b'\\u' + cp.lower().encode())
return list(seen)
def do_patch(binary):
import tempfile
# Parent of versions dir: same filesystem, outside the watched directory.
# All temp/backup writes go here to avoid inotify retriggers.
parent = os.path.dirname(os.path.dirname(binary))
version = os.path.basename(binary)
backup = os.path.join(parent, version + '.bak')
# Always read the live binary to check current state.
# If already patched, we find 0 matches and exit without writing,
# which avoids retriggering the systemd path watcher.
print(f"Reading from: {binary}")
with open(binary, 'rb') as f:
data = bytearray(f.read())
orig_size = len(data)
print(f"Binary size: {orig_size} bytes")
escapes = build_escape_list()
print(f"Searching for {len(escapes)} escape variants...")
total = 0
for esc in sorted(escapes):
count = 0
offset = 0
while True:
idx = data.find(esc, offset)
if idx == -1:
break
data[idx:idx+6] = ZWSP_6
count += 1
offset = idx + 6
if count > 0:
print(f" {esc.decode()}: {count}")
total += count
assert len(data) == orig_size, "Size mismatch after patching!"
print(f"\nTotal replacements: {total}")
if total == 0:
print("Already patched or no art found — skipping.")
return
# Back up the unpatched binary (only if we haven't already)
if not os.path.exists(backup):
print(f"Creating backup: {backup}")
with open(binary, 'rb') as f:
orig_data = f.read()
with open(backup, 'wb') as f:
f.write(orig_data)
os.chmod(backup, os.stat(binary).st_mode)
# Write temp file outside the watched versions dir
fd, patched = tempfile.mkstemp(prefix='claude-dehat-', dir=parent)
os.close(fd)
print(f"Writing to {patched}...")
with open(patched, 'wb') as f:
f.write(data)
os.chmod(patched, os.stat(binary).st_mode)
print(f"Atomic rename -> {binary}")
os.rename(patched, binary)
print(f"\nDone! {total} art escapes blanked.")
def do_restore(binary):
import shutil
import tempfile
parent = os.path.dirname(os.path.dirname(binary))
version = os.path.basename(binary)
backup = os.path.join(parent, version + '.bak')
# Also check old backup location for backwards compat
old_backup = binary + '.bak'
if not os.path.exists(backup) and os.path.exists(old_backup):
backup = old_backup
if not os.path.exists(backup):
sys.exit(f"ERROR: No backup found at {backup}")
fd, patched = tempfile.mkstemp(prefix='claude-dehat-', dir=parent)
os.close(fd)
shutil.copy2(backup, patched)
os.rename(patched, binary)
print(f"Restored {binary} from {backup}.")
STAMP_DIR = os.path.expanduser("~/.local/share/claude/dehat-stamps")
def stamp_path(binary):
return os.path.join(STAMP_DIR, os.path.basename(binary) + '.dehatted')
def write_stamp(binary):
os.makedirs(STAMP_DIR, exist_ok=True)
open(stamp_path(binary), 'w').close()
def main():
if '--restore' in sys.argv:
binary = resolve_binary()
print(f"Claude binary: {binary}")
do_restore(binary)
elif '--single' in sys.argv:
idx = sys.argv.index('--single')
binary = sys.argv[idx + 1]
if os.path.exists(stamp_path(binary)):
sys.exit(0)
do_patch(binary)
write_stamp(binary)
else:
binaries = find_all_binaries()
if not binaries:
sys.exit("ERROR: No binaries found in versions directory.")
print(f"Found {len(binaries)} binary/ies in {VERSIONS_DIR}")
for binary in binaries:
print(f"\n--- {os.path.basename(binary)} ---")
wait_for_stable(binary)
do_patch(binary)
write_stamp(binary)
if __name__ == '__main__':
main()
@yuxi-liu-wired
Copy link
Author

New installation script at

curl -fsSL 'https://gist.githubusercontent.com/yuxi-liu-wired/f4076b6f636d59a53b7e84d41d64397e/raw/d4f3e268bd5a6956b9e4ab63e34b8b132a98c0e9/erase_ascii_claude_code.sh' | bash

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