Skip to content

Commit 40d6d6e

Browse files
committed
config: verify.sh discovers the proxy port instead of defaulting it
The proxy-live check read ~/.claude.json for CLAUDE_CODE_CACHE_FIX_PORT. No such key exists — the launcher variable is CACHE_FIX_PROXY_PORT and it lives in the proxy process env, not that file. Measured on all three machines: 0 matches, so the sed produced nothing and ${port:-9901} supplied the default on every run the check has ever made. That is exactly the assumption its own comment forbids ("rather than assuming 9901 ... asserting the default would pass while every live session dialled a dead one"). It would have reported OK about a dead port on any machine whose proxy had moved. Now discovers <TMPDIR-or-/tmp>/cachefix-<port>, the same source cc-check-ccf uses, and probes every one it finds. No run dir is a FAIL, never a default — a check that cannot find its subject has not looked. Mutation-verified: no run dir -> FAIL exit 1; run dir with a dead port -> FAIL exit 1; real machine -> OK on the discovered 9901.
1 parent 23346ac commit 40d6d6e

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

.claude/deploy.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/bin/bash
2+
# CCF — put THIS machine on origin/integrated.
3+
#
4+
# Contract: exit 0 = on the branch; non-zero = not, reason on stdout; one line
5+
# naming the host and the sha.
6+
set -eu
7+
D="$HOME/.local/share/cache-fix-fork"
8+
9+
[ -d "$D/.git" ] || { echo "REFUSE: $(hostname -s):$D is not a checkout"; exit 1; }
10+
11+
# Refuse rather than resolve. `reset --hard` would answer a wrong-branch tree by
12+
# discarding whatever is there, and on this machine that tree is what every live
13+
# session's launcher runs from.
14+
branch=$(git -C "$D" rev-parse --abbrev-ref HEAD)
15+
[ "$branch" = "integrated" ] || {
16+
echo "REFUSE: $(hostname -s):$D is on '$branch' — a human decides this"; exit 1; }
17+
18+
git -C "$D" fetch origin integrated --quiet
19+
# reset, not merge --ff-only: the rebuild force-pushes `integrated` (it is
20+
# rebuilt from upstream/main every time, never patched), so the deployed copy is
21+
# routinely NOT a fast-forward from its own HEAD. --ff-only would refuse every
22+
# rebuild that dropped a merged PR. Safe here because nothing edits this tree by
23+
# hand — the wrong-branch guard above is what protects the case that matters.
24+
git -C "$D" reset --hard -q origin/integrated
25+
echo "$(hostname -s) @ $(git -C "$D" rev-parse --short HEAD)"
26+
27+
# Re-apply the usage-log opt-in. `usage-log` ships enabled:false in its own
28+
# export default — upstream's deliberate opt-in, activated by an entry in
29+
# proxy/extensions.json. That file is TRACKED, so the reset above reverts it and
30+
# the extension goes quiet with nothing to say it did.
31+
#
32+
# Measured 2026-08-01: ~/.claude/usage.jsonl last written 2026-07-04 — four weeks
33+
# of per-call token accounting missing while the proxy itself was healthy at 96%
34+
# cache hit rate, so /check-usage could not attribute a 5h window that had
35+
# reached 85%. Then measured again the same day: running the one-line
36+
# `reset --hard` deploy took usage-log from {"enabled":true} back to absent.
37+
# This block is why deploy is a script and not a conf string.
38+
python3 - "$D/proxy/extensions.json" <<'PY' || echo "WARN: $(hostname -s) could not enable usage-log (accounting only, proxy unaffected)"
39+
import json, sys
40+
p = sys.argv[1]
41+
with open(p) as f:
42+
cfg = json.load(f)
43+
if cfg.get("usage-log", {}).get("enabled") is not True:
44+
cfg["usage-log"] = {"enabled": True, "order": 650}
45+
with open(p, "w") as f:
46+
json.dump(cfg, f, indent=2)
47+
f.write("\n")
48+
PY
49+
50+
# Extensions are read ONCE at proxy boot and hot-reload is opt-in and off, so
51+
# flipping usage-log on disk does nothing for a proxy already running. Report it;
52+
# restarting drops in-flight requests for every session on that port and is a
53+
# separate, confirmed step.
54+
pgrep -f 'cache-fix-fork/proxy/server.mjs' >/dev/null 2>&1 &&
55+
echo "NOTE: $(hostname -s) has a live proxy holding pre-deploy code — it keeps it until relaunch"
56+
57+
exit 0

.claude/integrated.conf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# CCF — <repo>/.claude/integrated.conf
2+
#
3+
# Committed on `integrated-config`, the first input to every rebuild. Reaches
4+
# upstream never: every PR branch is cut from upstream/main, so there is no path
5+
# for this file onto one. Measured before relying on it —
6+
# git ls-tree ca-trust-guard .claude/ -> agent-name, github-app only
7+
# git reset --hard upstream/main -> a conf committed on `integrated`
8+
# is DESTROYED, which is why it lives
9+
# on its own branch instead.
10+
11+
UPSTREAM_REPO=cnighswonger/claude-code-cache-fix
12+
UPSTREAM_REMOTE=upstream
13+
UPSTREAM_BRANCH=main
14+
FORK_REMOTE=origin
15+
16+
INTEGRATED_BRANCH=integrated
17+
18+
PR_AUTHOR=codeslake
19+
20+
# The proxy prefix is load-bearing, not decoration: without it a test in
21+
# test/proxy-server.test.mjs makes a real api.anthropic.com request and hangs on
22+
# this network until it times out.
23+
TEST_CMD='HTTPS_PROXY=http://127.0.0.1:8118 NO_PROXY=localhost,127.0.0.1,::1 npm test'
24+
25+
TEST_CAVEAT='EMFILE in test/proxy-server.test.mjs is fs.inotify.max_user_instances (128 on lmd42), not a regression — confirm it fails identically at the merge base before calling it ours'
26+
27+
DEPLOY_HOSTS='lmd42-docker via-work-mac via-personal-mac'
28+
29+
POLL=300

.claude/verify.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/bin/bash
2+
# CCF — is the deploy actually LIVE on this machine?
3+
# One line per check: <name>\tOK|FAIL\t<detail>. Non-zero exit if any FAIL.
4+
set -u
5+
D="$HOME/.local/share/cache-fix-fork"
6+
CFG="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
7+
fail=0
8+
say() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; [ "$2" = FAIL ] && fail=1; return 0; }
9+
10+
# 1. The launcher parses at all. A tree that cannot even be loaded is the one
11+
# failure that makes every other probe meaningless.
12+
launcher="$D/bin/claude-via-proxy.mjs"
13+
if [ ! -r "$launcher" ]; then say launcher FAIL "absent: $launcher"
14+
elif out=$(node --check "$launcher" 2>&1); then say launcher OK "parses"
15+
else say launcher FAIL "${out:-parse failed}"; fi
16+
17+
# 2. The guard agrees with this machine's LIVE bundle.
18+
#
19+
# This is the check that earns its place: the bundle is written by a third
20+
# component (cc-wrapper's cachefix-ensure) from inputs neither this repo nor
21+
# the deploy controls, so a tree that is correct can still meet a bundle it
22+
# refuses. Probing bin/ca-trust.mjs — the module the launcher imports, not a
23+
# copy — is the point of it living in its own file.
24+
#
25+
# Absent bundle is OK, not FAIL: a host with no builder is a supported state
26+
# and the launcher falls back to its own CA. Absent CA is FAIL — forward-proxy
27+
# mode cannot work without one.
28+
if [ ! -r "$CFG/cache-fix-ca/ca.pem" ]; then
29+
say ca-trust FAIL "no proxy CA at $CFG/cache-fix-ca/ca.pem"
30+
elif [ ! -s "$CFG/ca-trust.pem" ]; then
31+
say ca-trust OK "no merged bundle on this host — launcher uses its own CA"
32+
else
33+
out=$(node -e '
34+
import(process.argv[1] + "/bin/ca-trust.mjs").then(async m => {
35+
const fs = await import("node:fs");
36+
const cfg = process.argv[2];
37+
const ca = fs.readFileSync(cfg + "/cache-fix-ca/ca.pem");
38+
const merged = fs.readFileSync(cfg + "/ca-trust.pem", "utf8");
39+
const n = (merged.match(/^-----BEGIN CERTIFICATE-----$/gm) || []).length;
40+
const v = m.bundleCarriesOurCA(merged, ca);
41+
console.log(n + " certs: " + (v.ok ? "accepted" : "REFUSED — " + v.reason));
42+
process.exit(v.ok ? 0 : 1);
43+
}).catch(e => { console.log("threw: " + e.message); process.exit(1); });
44+
' "$D" "$CFG" 2>&1)
45+
if [ $? -eq 0 ]; then say ca-trust OK "$out"; else say ca-trust FAIL "$out"; fi
46+
fi
47+
48+
# 3. A proxy answers, on the port the sessions were actually built against —
49+
# a session that started during a reload window baked whatever port it got,
50+
# and asserting the default would pass while every live session dialled a
51+
# dead one.
52+
#
53+
# The run dir `<TMPDIR-or-/tmp>/cachefix-<port>` is where that port is
54+
# recorded; it is the same discovery cc-check-ccf's probe uses. This read
55+
# ~/.claude.json for CLAUDE_CODE_CACHE_FIX_PORT until 2026-08-01, and no
56+
# such key exists — the launcher's variable is CACHE_FIX_PROXY_PORT and it
57+
# lives in the proxy's own env. Measured on all three machines: 0 matches,
58+
# so the sed produced nothing and `${port:-9901}` supplied the default every
59+
# single run. The check asserted precisely the assumption the paragraph
60+
# above forbids, and would have said OK about a dead port on any machine
61+
# whose proxy had moved.
62+
#
63+
# No run dir ⇒ FAIL, never a default. A check that cannot find its subject
64+
# has not looked; reporting that as OK is the whole failure mode.
65+
tmp=${TMPDIR:-/tmp}; tmp=${tmp%/}
66+
ports=$(for d in "$tmp"/cachefix-* /tmp/cachefix-*; do
67+
[ -d "$d" ] || continue
68+
p=${d##*cachefix-}
69+
case "$p" in ''|*[!0-9]*) continue ;; esac
70+
echo "$p"
71+
done | sort -un)
72+
if [ -z "$ports" ]; then
73+
say proxy-live FAIL "no cachefix-<port> run dir under $tmp or /tmp — nothing to probe"
74+
else
75+
for port in $ports; do
76+
if out=$(curl -sf --max-time 3 --noproxy '*' "http://127.0.0.1:$port/health" 2>&1); then
77+
case "$out" in
78+
*'"forward_proxy":true'*) say proxy-live OK "port $port: $out" ;;
79+
# Answering in reverse-proxy mode is not "up": that mode sets
80+
# ANTHROPIC_BASE_URL, which is what disables Remote Control.
81+
*) say proxy-live FAIL "port $port answers but forward_proxy is not true: $out" ;;
82+
esac
83+
else
84+
say proxy-live FAIL "run dir $tmp/cachefix-$port exists but nothing answers 127.0.0.1:$port"
85+
fi
86+
done
87+
fi
88+
89+
exit $fail

0 commit comments

Comments
 (0)