Skip to content

fix(patch): make clodex patch work on Claude Code 2.1.304 instead of refusing every platform - #332

Merged
bman654 merged 2 commits into
bman654:mainfrom
JohanCodinha:fix/patch-cc-2.1.304-upstream
Oct 20, 2026
Merged

bman654 merged 2 commits into
bman654:mainfrom
JohanCodinha:fix/patch-cc-2.1.304-upstream

Conversation

@JohanCodinha

@JohanCodinha JohanCodinha commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Claude Code 2.1.304 changed one of the internal functions clodex patches, so clodex patch refused to patch it at all: clodex models and aliases vanished from /model and the Agent tool, large-context models fell back to a 200K window, and commands Claude Code ran could inherit clodex's private proxy settings. This makes clodex patch recognise 2.1.304 on every platform again.

The failure

clodex patch on 2.1.304 (all eight published builds): FAIL PATCH 10: child network environment — anchor not found. PATCH 10 is required, so the whole patch was refused. The autopatch LaunchAgent hit it on the morning the update landed.

Root cause

2.1.304 gave Claude Code's shared child-environment builder an optional base-env parameter and spread the parent env over it:

function is(e){let n=O.of(j().host),r=n.getAgentProxyEnv?.()??{},…,i=process.env;
if(_||e!==void 0){i={...e,...process.env};…}
… if(e===void 0&&!o&&…)return process.env;let N={...r,...u},d={...i};…return d}

PATCH 10's head only admitted a zero-arg function X(){, and its body check required the literal {...process.env, which this body no longer contains. The builder is is(e) with the identical shape in all eight builds.

The change

  • Head: function [\w$]+\(\)\{ → function [\w$]+\([\w$]*\)\{, so exactly one plain parameter. Defaults, destructuring and multiple parameters still refuse loudly, and a test pins the two-parameter refusal.
  • Body check: '{...process.env' → '...process.env'.
  • PATCH_TRANSFORMS_VERSION 15 → 16, digest re-pinned.
  • .claude/harnesses/pr78-patch10-anchor-over-real-bundles.harness.ts: its zero-arg boundFunction check now admits one parameter, and its two stale ../src paths are corrected to ../../src so it loads again.

What the parameter carries. (Corrected by the maintainer; the original paragraph said the only call passing the parameter reads just the key set, which is wrong.) Two calls pass the parameter in all eight builds, both with the shell snapshot's exports. Object.keys(is(this.#r)) reads only the key set. The Bash tool's spawn env, {...is(w),SHELL:…}, carries the values into the command when the exports are collected (desktop host entrypoints, not Windows). On the clodex-only path that is still safe: the snapshot shell is spawned with the builder's restored env, so it does not inherit clodex's proxy, CA or contract, and its exports keep only names the builder leaves unset when they are parsed. That is a property of Claude Code's callers, not something the transform enforces; patcher.md has the details. A text search for callers misses this one because it is a spread (...is(w)), as are 18 of the 48 no-argument calls.

Evidence

  • Unit tests (tests/patcher.test.ts): a 2.1.304-shaped fixture, execution with and without a base env (the restored proxy wins over a base carrying the injected one), a one-parameter decoy that must not steal the match, and the two-parameter refusal. Full-file mutation runs: reverting the head reddens 4 behavioural tests, reverting the body literal reddens 5, and widening the head to [^)]* reddens the refusal test. None of these relies on the digest pin alone.
  • Real bundles (.claude/harnesses/cc304-patch10-execute-real-builder.harness.ts, extracted from all eight 2.1.304 builds downloaded from downloads.claude.ai and checksum-verified against the release manifest): every patch site applies, the patched builder compiles, the token skeleton matches across builds, and it executes correctly on the early-return, credential-scrub, base-env and settings-env branches plus hostile contracts. 146/146, plus 9/9 on the widened pr78 anchor harness.
  • Byte-identical: applyClodexPatches output on real 2.1.296, 2.1.299, 2.1.300, 2.1.301, 2.1.302 and 2.1.303 (darwin-arm64) is identical to version 15's. The widened head matches exactly one function in every one of those bundles and in all eight 2.1.304 builds.
  • Formats: scripts/probe-patch-mechanism.mjs --expect-version 2.1.304 PASS on darwin-arm64, darwin-x64, linux-arm64, linux-arm64-musl, linux-x64, linux-x64-musl, win32-arm64 and win32-x64 (17/17 checks each, all 12 sites applied).
  • End to end, maintainer re-run (macOS arm64, sandboxed HOME, a stand-in Anthropic endpoint, clodex-shaped injected env set by hand): through the pristine 2.1.304 a Bash tool call prints the injected HTTPS_PROXY, NODE_EXTRA_CA_CERTS and contract, and the Agent tool's model enum has four entries; through the patched one the child sees none of them and the enum carries the clodex aliases. This runs under the CLI entrypoint, where the shell exports are not collected, so the base-env path is covered by executing the extracted builder and parser rather than by a live desktop session.
  • End to end (macOS arm64, Node 22.23.2, proxy mode, measured on a build whose src/patch-transforms.ts and src/network-env.ts are byte-identical to this branch's, launched with the proxy/CA/contract env vars unset): clodex-claude --model haiku -p running env | grep -iE '^(https?_proxy|node_extra_ca_certs|CLAUDE_CODE_CLODEX_NETWORK_ENV)=' in a Bash tool call. Through the pristine 2.1.304 the child shell printed HTTPS_PROXY=http://127.0.0.1:17645, NODE_EXTRA_CA_CERTS=…/clodex-ca.pem and the contract; through the patched one it printed nothing. The translated leg (gpt-5.6-luna) also answers on the patched binary.
  • On this branch (upstream main at 2.21.1 plus this commit): pnpm typecheck, pnpm build and CLODEX_HOME=$(mktemp -d) pnpm test pass (3469 passed, 20 skipped), and both harnesses pass over the eight 2.1.304 bundles (155/155).

Not verified

  • The patched binary was only run on macOS arm64. Linux and Windows are covered by the probe (container round-trip and patch sites), which never executes the binary.
  • Behaviour if a later release passes a base env whose values reach a child: see the caveat above.

JohanCodinha and others added 2 commits October 20, 2026 10:09
…refusing every platform

Claude Code 2.1.304 gave its shared child-environment builder an optional
base-env parameter (`function is(e){…i={...e,...process.env}…}`). PATCH 10's
head admitted only a zero-arg function and its body check required a literal
`{...process.env`, so the required patch refused all eight published builds and
clodex patch left Claude Code unpatched.

The head now admits one plain parameter and the body check asks only that the
body spread `...process.env`. Transforms version 15 -> 16. Patched output over
2.1.296..2.1.303 is byte-identical to version 15's; the widened head still
matches exactly one function in every bundle measured.

In all eight 2.1.304 builds the only call passing the parameter reads just
`Object.keys` of the result, so the restore still decides which network values
a child command sees. Verified end to end: through the pristine 2.1.304 a child
shell sees clodex's injected proxy, CA and contract; through the patched one it
sees none of them.

The pr78 anchor harness also gets its two stale `../src` paths corrected to
`../../src` so it loads from .claude/harnesses/ again.
…er a base env

The version-16 comment and both docs said the only call passing the child-env builder's new
parameter reads just the key set. The Bash tool's spawn env passes it too (`{...is(w),…}` with the
shell snapshot's exports, collected under the desktop host), and those values reach the command.
Say why that is still safe on the clodex-only path instead: the snapshot shell is spawned with the
restored env, and its exports keep only names the builder leaves unset when they are parsed. The
existing settings-env and remote agent-proxy overlays still apply.

Re-pin the transform-source digest for the comment edit (version stays 16), and point the cc304
harness's run instructions at /tmp so they leave no config file in the repo root.

@bman654 bman654 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Thanks, Johan. This is the right root-cause fix, and an independent fix written before this PR was seen landed on the same two changes.

Verified on the maintainer side, all against a pristine 2.1.304:

  • Canary matrix, all 8 platforms: the real clodex patch applies 12/12 sites on darwin-arm64 (host) and on linux-arm64 and linux-arm64-musl (containers), and those patched binaries start. The probe passes on all 8.
  • Older releases: across 89 bundles from 2.1.208 to 2.1.302, version 16's output is byte-identical to version 15's. The widened head matches exactly one function in every one of them.
  • Mutations: reverting the head turns 4 behavioural tests red, and reverting the body literal turns 5 red. Widening the head to several parameters is caught by the two-parameter test. The harness passes 146/146 on all eight bundles.
  • Runtime A/B on darwin-arm64: a stand-in Anthropic endpoint and a clodex-shaped injected env. On pristine, a Bash tool call prints the injected proxy, CA and contract, and the Agent tool offers 4 models. On patched, the child sees none of them and the Agent tool offers the clodex aliases.

One correction, which I pushed to your branch rather than asking for another round. The PR said the only call passing the new parameter reads just the key set. A second call passes it: the Bash tool's spawn env, {...is(w),SHELL:…}, with the shell snapshot's exports, and those values reach the command. A text search misses it because it's a spread (...is(w)), as are 18 of the 48 no-argument calls.

It's still safe on the clodex-only path. The snapshot shell is spawned with the patched builder's restored env, and its exports keep only names the builder leaves unset. The commit rewrites the version-16 comment, patcher.md and claude-code-internals.md to give that reason, scoped to the clodex-only path. It also re-pins the source digest (version stays 16) and moves the harness's run config to /tmp. I also corrected the paragraph in the PR description. The corrected docs were fact-checked against all eight bundles by an independent reviewer.

@bman654
bman654 merged commit 102df08 into bman654:main Oct 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants