Second and final PR for mosaic [yolo] claudex — running GPT models inside the Claude Code harness via raine/claude-code-proxy (ChatGPT-subscription OAuth). Builds on the PR-1 preflight/lifecycle library (#793, merged 59f5f51f). This PR makes the command reachable.
Branch is off main at exactly 59f5f51f (PR-1 merge). Diff vs origin/main = only the 5 files below.
Scope
NEWpackages/mosaic/src/commands/claudex.ts — pure / dependency-injected composition (mirrors PR-1 style; every side-effecting boundary injected).
HARD SECURITY REQ 1 — provable isolation (assertIsolatedConfigDir / resolveClaudexConfigDir)
The isolated CLAUDE_CONFIG_DIR seam is provably incapable of resolving to the real ~/.claude:
Default dir ~/.config/mosaic/claudex/home — structurally under mosaic home. Ambient CLAUDE_CONFIG_DIR is never inherited; only MOSAIC_CLAUDEX_CONFIG_DIR overrides.
Guard canonicalizes BOTH sides (either may be a symlink — same realpath-both-sides rule that fixed PR-1's F2a) and rejects the candidate if it equals OR is a descendant of~/.claude. Empty/relative → throw.
TOCTOU: after mkdir -p (0700) it re-checks — rejects a symlinked target and re-runs the guard on the now-existing path (catches a symlinked ancestor introduced between pre-check and create). Fails closed on any post-create failure.
Test asserts the guard rejects literal ~/.claude, a descendant, a symlink-canonical match, and canonicalizes the ~/.claude side too; a claudex launch can never emit ~/.claude.
HARD SECURITY REQ 2 — zero token leakage (buildClaudexEnv)
This module never reads the proxy's auth.json / any credential file.
The composed env strips the entire credential-bearing family by name pattern (^ANTHROPIC_, ^CLAUDE_CODE_OAUTH, *_API_KEY, *_TOKEN, *_SECRET) — not just two vars — then re-sets only the safe claudex vars. Claude Code sees only ANTHROPIC_AUTH_TOKEN=unused; the proxy holds the real credential. ANTHROPIC_API_KEY is dropped by the pattern.
Returns a fresh object (no mutation of the base env). Tests assert: only auth var emitted is literal unused; a pre-set real token is overwritten; no composed value matches token shapes (sk-, eyJ, access_token, refresh_token); and no credential-named key survives; harmless env (PATH/HOME/XDG) is preserved.
Env-sweep design note (for secrev): denylist-by-name-pattern was chosen over a strict allowlist because the harness/MCP/hooks legitimately need arbitrary non-credential env (PATH, HOME, XDG, terminal, proxies…). The pattern is comprehensive and name-pattern coverage is asserted directly, so a specific var a denylist might forget is still caught by shape+name tests.
Console banner + a composed-contract note appended after composeContract('claude') so the model knows it is GPT-in-Claude-harness via claude-code-proxy (not Anthropic). No token material in either by construction.
Fail-closed orchestration (launchClaudex)
harnessPreflight → proxyGate → compose env → exec. exec is injected, so ordering + abort paths are unit-tested: gate-not-ok → abort before exec; config-dir guard throw → abort before exec; reauth invoked only when OAuth state needs it. Proxy endpoint imported from PR-1's claudex-proxy.ts (single source of truth — no re-hardcoded 18765/127.0.0.1).
Tests / gates
45 claudex specs + 5 launch wiring specs, TDD red-first (spec RED before claudex.ts existed; wiring RED before dispatch).
New-module coverage: 99.03% stmts/lines, 93.33% branch (≥85% gate; the isolation-guard and env-sweep branches are the ones hit — the only uncovered lines are a defensive canonicalize root-walk unreachable on a real FS).
Full @mosaicstack/mosaic suite: 1164 passing. pnpm typecheck / lint / format:check: green. No --no-verify.
## PR-2 — claudex isolated config + env-inject + `yolo` wiring (P2–P4 of #790)
Second and final PR for `mosaic [yolo] claudex` — running GPT models **inside the Claude Code harness** via `raine/claude-code-proxy` (ChatGPT-subscription OAuth). Builds on the PR-1 preflight/lifecycle library (#793, merged `59f5f51f`). This PR makes the command reachable.
Branch is off `main` at exactly `59f5f51f` (PR-1 merge). Diff vs `origin/main` = only the 5 files below.
### Scope
- **NEW** `packages/mosaic/src/commands/claudex.ts` — pure / dependency-injected composition (mirrors PR-1 style; every side-effecting boundary injected).
- **EDIT** `launch.ts` — `execRuntime` env param; `claudex` + `yolo claudex` dispatch; thin `launchClaudexProduction` glue.
- **EDIT** `launch.spec.ts` — commander wiring tests (claudex handler spy; `yolo claudex` #454 arg-slice).
- **NEW** `claudex.spec.ts` — 45 specs. **DOCS** `README.md`.
### HARD SECURITY REQ 1 — provable isolation (`assertIsolatedConfigDir` / `resolveClaudexConfigDir`)
The isolated `CLAUDE_CONFIG_DIR` seam is provably incapable of resolving to the real `~/.claude`:
- Default dir `~/.config/mosaic/claudex/home` — structurally under mosaic home. Ambient `CLAUDE_CONFIG_DIR` is **never** inherited; only `MOSAIC_CLAUDEX_CONFIG_DIR` overrides.
- Guard **canonicalizes BOTH sides** (either may be a symlink — same realpath-both-sides rule that fixed PR-1's F2a) and rejects the candidate if it **equals OR is a descendant of** `~/.claude`. Empty/relative → throw.
- **TOCTOU:** after `mkdir -p` (0700) it re-checks — rejects a symlinked target and **re-runs the guard** on the now-existing path (catches a symlinked ancestor introduced between pre-check and create). Fails **closed** on any post-create failure.
- Test asserts the guard rejects literal `~/.claude`, a descendant, a symlink-canonical match, and canonicalizes the `~/.claude` side too; a claudex launch can never emit `~/.claude`.
### HARD SECURITY REQ 2 — zero token leakage (`buildClaudexEnv`)
- This module **never reads** the proxy's `auth.json` / any credential file.
- The composed env **strips the entire credential-bearing family** by name pattern (`^ANTHROPIC_`, `^CLAUDE_CODE_OAUTH`, `*_API_KEY`, `*_TOKEN`, `*_SECRET`) — not just two vars — then re-sets only the safe claudex vars. Claude Code sees only `ANTHROPIC_AUTH_TOKEN=unused`; the proxy holds the real credential. `ANTHROPIC_API_KEY` is dropped by the pattern.
- Returns a **fresh object** (no mutation of the base env). Tests assert: only auth var emitted is literal `unused`; a pre-set real token is overwritten; no composed value matches token shapes (`sk-`, `eyJ`, `access_token`, `refresh_token`); **and** no credential-*named* key survives; harmless env (PATH/HOME/XDG) is preserved.
> **Env-sweep design note (for secrev):** denylist-by-name-pattern was chosen over a strict allowlist because the harness/MCP/hooks legitimately need arbitrary non-credential env (PATH, HOME, XDG, terminal, proxies…). The pattern is comprehensive and name-pattern coverage is asserted directly, so a specific var a denylist might forget is still caught by shape+name tests.
### P3 — model-tier map
primary (opus/sonnet) → `ANTHROPIC_MODEL=gpt-5.6-sol`; small/fast (haiku) → `ANTHROPIC_SMALL_FAST_MODEL=gpt-5.6-luna`. Operator-provided env values **win**; blank ignored.
### P4 — EXPERIMENTAL classification
Console banner + a composed-contract note appended after `composeContract('claude')` so the model knows it is GPT-in-Claude-harness via claude-code-proxy (not Anthropic). No token material in either by construction.
### Fail-closed orchestration (`launchClaudex`)
`harnessPreflight → proxyGate → compose env → exec`. `exec` is injected, so ordering + abort paths are unit-tested: gate-not-ok → abort **before** exec; config-dir guard throw → abort before exec; reauth invoked only when OAuth state needs it. Proxy endpoint imported from PR-1's `claudex-proxy.ts` (single source of truth — no re-hardcoded `18765`/`127.0.0.1`).
### Tests / gates
- 45 `claudex` specs + 5 `launch` wiring specs, **TDD red-first** (spec RED before `claudex.ts` existed; wiring RED before dispatch).
- New-module coverage: **99.03% stmts/lines, 93.33% branch** (≥85% gate; the isolation-guard and env-sweep branches are the ones hit — the only uncovered lines are a defensive canonicalize root-walk unreachable on a real FS).
- Full `@mosaicstack/mosaic` suite: **1164 passing**. `pnpm typecheck` / `lint` / `format:check`: green. No `--no-verify`.
### Review asks
- Independent reviewer (author ≠ reviewer), **exact-head** review + secrev on REQ 1 / REQ 2.
- No self-merge — merge authorized by coordinator (MS-LEAD) on APPROVE + green CI (queue guard first).
Closes #790 once merged (P1–P4 all land).
Second and final PR for `mosaic [yolo] claudex` — runs GPT models inside the
Claude Code harness via claude-code-proxy (ChatGPT-subscription OAuth). Builds
on the PR-1 preflight/lifecycle library (#793).
New `claudex.ts` (pure / dependency-injected composition):
- resolveClaudexConfigDir / assertIsolatedConfigDir — the isolated
CLAUDE_CONFIG_DIR seam. HARD REQ 1 (provable isolation): canonicalizes both
sides, rejects `~/.claude` and any descendant, ensures the dir 0700, then
re-checks post-create and rejects a symlinked target (TOCTOU). Ambient
CLAUDE_CONFIG_DIR is never inherited. Fails closed on any uncertainty.
- buildClaudexEnv — HARD REQ 2 (zero token leakage): strips the entire
credential-bearing env family (ANTHROPIC_*, *_TOKEN, *_API_KEY, *_SECRET,
CLAUDE_CODE_OAUTH*) and hands Claude Code only ANTHROPIC_AUTH_TOKEN=unused.
Never reads the proxy credential file. Returns a fresh object (no mutation).
- resolveClaudexModels — P3 tier map: primary→gpt-5.6-sol,
small/fast→gpt-5.6-luna; operator env values win.
- buildClaudexBanner / buildClaudexContractNote — P4 EXPERIMENTAL classification
(no token material by construction).
- runClaudexProxyGate — preflight → device reauth (only when needed) → ensure a
trusted-live listener → re-preflight; surfaces non-sensitive problems only.
- launchClaudex — fail-closed DI orchestration: preflight → gate → compose env →
exec; never reaches exec on a gate failure or guard throw. Proxy endpoint
imported from claudex-proxy.ts (single source of truth).
launch.ts wiring:
- execRuntime gains an env parameter.
- `claudex` + `yolo claudex` commander dispatch (keeps the #454 arg-slice fix);
claudexHandler is an injectable seam for the wiring tests.
- launchClaudexProduction glue assembles the real harness adapter.
Tests: 45 claudex specs + 5 launch wiring specs, TDD red-first. New-module
coverage 99% stmts/lines, 93% branch (≥85% gate). Full mosaic suite 1164 green;
typecheck / lint / format:check green.
Refs #790
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Addresses MS-LEAD secrev REQUEST CHANGES on #806.
S1 (CRITICAL — REQ 2 defeated by provider switches + cloud creds): the env sweep
did not neutralize AWS Bedrock / GCP Vertex routing. CLAUDE_CODE_USE_BEDROCK /
CLAUDE_CODE_USE_VERTEX are routing switches whose mere presence makes Claude Code
talk to the real Anthropic API via the ambient cloud credential chain, bypassing
ANTHROPIC_BASE_URL (the loopback proxy) entirely — and the cloud credentials
(AWS_SECRET_ACCESS_KEY, AWS_BEARER_TOKEN_BEDROCK, GOOGLE_APPLICATION_CREDENTIALS,
…) passed straight through.
- buildClaudexEnv now force-deletes the routing switches by exact name
(CLAUDEX_FORCED_UNSET_ENV) regardless of value — a name pattern is the wrong
model for a boolean switch.
- CLAUDEX_CREDENTIAL_ENV_RE extended to the cloud-cred families (^AWS_,
^GOOGLE_APPLICATION_CREDENTIALS, ^GOOGLE_CLOUD_, ^GCP_) and the mid-string
_KEY$ / _SECRET gap (STRIPE_SECRET_KEY, SSH_PRIVATE_KEY, AWS_SECRET_ACCESS_KEY).
- Doc-comment updated to match reality.
S2 (HIGH — fail-open contradicts documented fail-closed): defaultCanonicalizeIntended
and defaultIsSymlink swallowed ANY fs error. Now they distinguish ENOENT
(genuinely absent → safe to continue) from every other errno (ELOOP, EACCES,
ENOTDIR, …) which rethrows and fails CLOSED.
Tests (TDD red-first, 4 new specs red before the fix): Bedrock/Vertex switches +
AWS/GCP creds swept while the loopback proxy stays the only route; mid-string
_KEY/_SECRET closed; real-FS ELOOP (canonicalize) and ENOTDIR (isSymlink) fail
closed; injected EACCES not swallowed.
New-module coverage 100% stmts/lines, 93.75% branch. Full mosaic suite 1169 green;
typecheck / lint / format:check green. No --no-verify.
Refs #790
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Exact-head independent review + security review — APPROVED @ 79e2fa35d547274bc877a8e51b0d8c59ea302025 (reviewer: mosaic-100; author: ms-claudex-790 — author≠reviewer). Merged clean+green under coordinated-lane self-merge authority. Publishing the exact-head record for provenance (prior REQUEST CHANGES @ ab8c9a2d did not transfer; this is a fresh review of the exact SHA).
Provenance: head 79e2fa35 = exactly one fix commit on ab8c9a2d, no rebase/amend; diff vs origin/main = 5 files, all under packages/mosaic/. CI pipeline 1873 terminal-green at this exact head.
S1 (was CRITICAL — REQ 2 Bedrock/Vertex bypass) — RESOLVED by construction.buildClaudexEnv now force-deletes the routing switches CLAUDE_CODE_USE_BEDROCK / USE_VERTEX / SKIP_BEDROCK_AUTH / SKIP_VERTEX_AUTH by exact name regardless of value (presence, not credential-ness, is the defeat — correct model), applied after the credential sweep and before the ANTHROPIC_* re-sets. CLAUDEX_CREDENTIAL_ENV_RE extended to ^AWS_, ^GOOGLE_APPLICATION_CREDENTIALS$, ^GOOGLE_CLOUD_, ^GCP_, plus _KEY$ and de-anchored _SECRET (closes mid-string AWS_SECRET_ACCESS_KEY / STRIPE_SECRET_KEY / SSH_PRIVATE_KEY). Independent adversarial secrev swept the full Bedrock/Vertex/direct provider surface and could NOT defeat REQ 2: every credential stripped, every routing switch force-deleted, surviving vars (CLOUD_ML_REGION/VERTEX_REGION) inert without the force-unset switches, ANTHROPIC_BASE_URL pinned to the loopback proxy, opts.baseUrl not env-derived, isolated CLAUDE_CONFIG_DIR freshly 0700.
S2 (was HIGH — fail-open errno swallow) — RESOLVED.defaultCanonicalizeIntended and defaultIsSymlink now distinguish ENOENT (genuinely absent → walk up / not-a-symlink) from every other errno (ELOOP/EACCES/ENOTDIR → rethrow → fail CLOSED). Rethrow propagates to launchClaudex's catch → fail(1).
Tests: 4 new red-first specs, non-tautological — S1 asserts both switch/cred removal AND proxy-URL + unused + PATH preservation; S2 uses real-FS ELOOP/ENOTDIR + injected EACCES asserting toThrow. Red against ab8c9a2d, green after fix; no test edited to pass. New-module coverage 100% stmts/lines, 93.75% branch; full mosaic suite 1169 passing; typecheck/lint/format ✓; no --no-verify; queue guard run before push.
Non-blocking follow-up:CLAUDEX_FORCED_UNSET_ENV is a name-denylist with no pattern safety-net — add a guard/test if Claude Code introduces a new provider-enabling switch. Not gating.
**Exact-head independent review + security review — APPROVED @ `79e2fa35d547274bc877a8e51b0d8c59ea302025`** (reviewer: mosaic-100; author: ms-claudex-790 — author≠reviewer). Merged clean+green under coordinated-lane self-merge authority. Publishing the exact-head record for provenance (prior REQUEST CHANGES @ `ab8c9a2d` did not transfer; this is a fresh review of the exact SHA).
**Provenance:** head `79e2fa35` = exactly one fix commit on `ab8c9a2d`, no rebase/amend; diff vs origin/main = 5 files, all under `packages/mosaic/`. CI pipeline 1873 terminal-green at this exact head.
**S1 (was CRITICAL — REQ 2 Bedrock/Vertex bypass) — RESOLVED by construction.** `buildClaudexEnv` now force-deletes the routing switches `CLAUDE_CODE_USE_BEDROCK` / `USE_VERTEX` / `SKIP_BEDROCK_AUTH` / `SKIP_VERTEX_AUTH` by exact name regardless of value (presence, not credential-ness, is the defeat — correct model), applied after the credential sweep and before the ANTHROPIC_* re-sets. `CLAUDEX_CREDENTIAL_ENV_RE` extended to `^AWS_`, `^GOOGLE_APPLICATION_CREDENTIALS$`, `^GOOGLE_CLOUD_`, `^GCP_`, plus `_KEY$` and de-anchored `_SECRET` (closes mid-string `AWS_SECRET_ACCESS_KEY` / `STRIPE_SECRET_KEY` / `SSH_PRIVATE_KEY`). Independent adversarial secrev swept the full Bedrock/Vertex/direct provider surface and could NOT defeat REQ 2: every credential stripped, every routing switch force-deleted, surviving vars (CLOUD_ML_REGION/VERTEX_REGION) inert without the force-unset switches, ANTHROPIC_BASE_URL pinned to the loopback proxy, opts.baseUrl not env-derived, isolated CLAUDE_CONFIG_DIR freshly 0700.
**S2 (was HIGH — fail-open errno swallow) — RESOLVED.** `defaultCanonicalizeIntended` and `defaultIsSymlink` now distinguish `ENOENT` (genuinely absent → walk up / not-a-symlink) from every other errno (`ELOOP`/`EACCES`/`ENOTDIR` → rethrow → fail CLOSED). Rethrow propagates to `launchClaudex`'s catch → `fail(1)`.
**Tests:** 4 new red-first specs, non-tautological — S1 asserts both switch/cred removal AND proxy-URL + `unused` + PATH preservation; S2 uses real-FS ELOOP/ENOTDIR + injected EACCES asserting `toThrow`. Red against `ab8c9a2d`, green after fix; no test edited to pass. New-module coverage 100% stmts/lines, 93.75% branch; full mosaic suite 1169 passing; typecheck/lint/format ✓; no `--no-verify`; queue guard run before push.
**Non-blocking follow-up:** `CLAUDEX_FORCED_UNSET_ENV` is a name-denylist with no pattern safety-net — add a guard/test if Claude Code introduces a new provider-enabling switch. Not gating.
Closes #790.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PR-2 — claudex isolated config + env-inject +
yolowiring (P2–P4 of #790)Second and final PR for
mosaic [yolo] claudex— running GPT models inside the Claude Code harness viaraine/claude-code-proxy(ChatGPT-subscription OAuth). Builds on the PR-1 preflight/lifecycle library (#793, merged59f5f51f). This PR makes the command reachable.Branch is off
mainat exactly59f5f51f(PR-1 merge). Diff vsorigin/main= only the 5 files below.Scope
packages/mosaic/src/commands/claudex.ts— pure / dependency-injected composition (mirrors PR-1 style; every side-effecting boundary injected).launch.ts—execRuntimeenv param;claudex+yolo claudexdispatch; thinlaunchClaudexProductionglue.launch.spec.ts— commander wiring tests (claudex handler spy;yolo claudex#454 arg-slice).claudex.spec.ts— 45 specs. DOCSREADME.md.HARD SECURITY REQ 1 — provable isolation (
assertIsolatedConfigDir/resolveClaudexConfigDir)The isolated
CLAUDE_CONFIG_DIRseam is provably incapable of resolving to the real~/.claude:~/.config/mosaic/claudex/home— structurally under mosaic home. AmbientCLAUDE_CONFIG_DIRis never inherited; onlyMOSAIC_CLAUDEX_CONFIG_DIRoverrides.~/.claude. Empty/relative → throw.mkdir -p(0700) it re-checks — rejects a symlinked target and re-runs the guard on the now-existing path (catches a symlinked ancestor introduced between pre-check and create). Fails closed on any post-create failure.~/.claude, a descendant, a symlink-canonical match, and canonicalizes the~/.claudeside too; a claudex launch can never emit~/.claude.HARD SECURITY REQ 2 — zero token leakage (
buildClaudexEnv)auth.json/ any credential file.^ANTHROPIC_,^CLAUDE_CODE_OAUTH,*_API_KEY,*_TOKEN,*_SECRET) — not just two vars — then re-sets only the safe claudex vars. Claude Code sees onlyANTHROPIC_AUTH_TOKEN=unused; the proxy holds the real credential.ANTHROPIC_API_KEYis dropped by the pattern.unused; a pre-set real token is overwritten; no composed value matches token shapes (sk-,eyJ,access_token,refresh_token); and no credential-named key survives; harmless env (PATH/HOME/XDG) is preserved.P3 — model-tier map
primary (opus/sonnet) →
ANTHROPIC_MODEL=gpt-5.6-sol; small/fast (haiku) →ANTHROPIC_SMALL_FAST_MODEL=gpt-5.6-luna. Operator-provided env values win; blank ignored.P4 — EXPERIMENTAL classification
Console banner + a composed-contract note appended after
composeContract('claude')so the model knows it is GPT-in-Claude-harness via claude-code-proxy (not Anthropic). No token material in either by construction.Fail-closed orchestration (
launchClaudex)harnessPreflight → proxyGate → compose env → exec.execis injected, so ordering + abort paths are unit-tested: gate-not-ok → abort before exec; config-dir guard throw → abort before exec; reauth invoked only when OAuth state needs it. Proxy endpoint imported from PR-1'sclaudex-proxy.ts(single source of truth — no re-hardcoded18765/127.0.0.1).Tests / gates
claudexspecs + 5launchwiring specs, TDD red-first (spec RED beforeclaudex.tsexisted; wiring RED before dispatch).@mosaicstack/mosaicsuite: 1164 passing.pnpm typecheck/lint/format:check: green. No--no-verify.Review asks
Closes #790 once merged (P1–P4 all land).
Exact-head independent review + security review — APPROVED @
79e2fa35d547274bc877a8e51b0d8c59ea302025(reviewer: mosaic-100; author: ms-claudex-790 — author≠reviewer). Merged clean+green under coordinated-lane self-merge authority. Publishing the exact-head record for provenance (prior REQUEST CHANGES @ab8c9a2ddid not transfer; this is a fresh review of the exact SHA).Provenance: head
79e2fa35= exactly one fix commit onab8c9a2d, no rebase/amend; diff vs origin/main = 5 files, all underpackages/mosaic/. CI pipeline 1873 terminal-green at this exact head.S1 (was CRITICAL — REQ 2 Bedrock/Vertex bypass) — RESOLVED by construction.
buildClaudexEnvnow force-deletes the routing switchesCLAUDE_CODE_USE_BEDROCK/USE_VERTEX/SKIP_BEDROCK_AUTH/SKIP_VERTEX_AUTHby exact name regardless of value (presence, not credential-ness, is the defeat — correct model), applied after the credential sweep and before the ANTHROPIC_* re-sets.CLAUDEX_CREDENTIAL_ENV_REextended to^AWS_,^GOOGLE_APPLICATION_CREDENTIALS$,^GOOGLE_CLOUD_,^GCP_, plus_KEY$and de-anchored_SECRET(closes mid-stringAWS_SECRET_ACCESS_KEY/STRIPE_SECRET_KEY/SSH_PRIVATE_KEY). Independent adversarial secrev swept the full Bedrock/Vertex/direct provider surface and could NOT defeat REQ 2: every credential stripped, every routing switch force-deleted, surviving vars (CLOUD_ML_REGION/VERTEX_REGION) inert without the force-unset switches, ANTHROPIC_BASE_URL pinned to the loopback proxy, opts.baseUrl not env-derived, isolated CLAUDE_CONFIG_DIR freshly 0700.S2 (was HIGH — fail-open errno swallow) — RESOLVED.
defaultCanonicalizeIntendedanddefaultIsSymlinknow distinguishENOENT(genuinely absent → walk up / not-a-symlink) from every other errno (ELOOP/EACCES/ENOTDIR→ rethrow → fail CLOSED). Rethrow propagates tolaunchClaudex's catch →fail(1).Tests: 4 new red-first specs, non-tautological — S1 asserts both switch/cred removal AND proxy-URL +
unused+ PATH preservation; S2 uses real-FS ELOOP/ENOTDIR + injected EACCES assertingtoThrow. Red againstab8c9a2d, green after fix; no test edited to pass. New-module coverage 100% stmts/lines, 93.75% branch; full mosaic suite 1169 passing; typecheck/lint/format ✓; no--no-verify; queue guard run before push.Non-blocking follow-up:
CLAUDEX_FORCED_UNSET_ENVis a name-denylist with no pattern safety-net — add a guard/test if Claude Code introduces a new provider-enabling switch. Not gating.Closes #790.