From 4acd56c4b6765101ec20c6e33c397a5b5a5196b2 Mon Sep 17 00:00:00 2001 From: Scott Karp Date: Thu, 15 Oct 2026 19:12:04 +0000 Subject: [PATCH] kohen: fallback for Claude Code when the gate is down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every Claude Code session on the dev VM goes through bikoret.ch; when the pod is not ready the session cannot reach Anthropic (2026-10-15: Doorpost drift → 503 → the transmitter locked out, recovered by hand). An `env` entry in ~/.claude/settings.json overrides the shell environment (verified), so `env -u … claude` is not a recovery while the gate is armed there. - scripts/claude-direct: start straight to Anthropic via a --settings JSON on the command line, which outranks every settings file. - scripts/claude-bikoret: probe the gate first; if it does not answer, say so and exec claude-direct (BIKORET_STRICT=1 refuses instead). - scripts/bikoret-settings on|off|status: arm/disarm the gate for every launcher; `on` writes the key (kohen only), `off` is the recovery. - KOHEN.md: the fallback and the order of recovery. Tested 2026-10-15: dead gate armed in settings → plain claude hangs, claude-direct answers; probe at a dead host → fallback + answer; live gate → answer with the fringe. Co-Authored-By: Claude Fable 5.1 --- KOHEN.md | 28 ++++++++++++++++++++++++++++ scripts/bikoret-settings | 33 +++++++++++++++++++++++++++++++++ scripts/claude-bikoret | 27 +++++++++++++++++++++++++++ scripts/claude-direct | 12 ++++++++++++ 4 files changed, 100 insertions(+) create mode 100755 scripts/bikoret-settings create mode 100755 scripts/claude-bikoret create mode 100755 scripts/claude-direct diff --git a/KOHEN.md b/KOHEN.md index 424b9bd6..1e833320 100644 --- a/KOHEN.md +++ b/KOHEN.md @@ -620,6 +620,34 @@ Each upstream call spends one `api_calls_external` of the day's manna the bedikah by hand still runs. Streaming is refused (422). A non-200 from the model passes through as it came. Tamid §1.1 has the contract. +**Fallback — when the gate is down, Claude Code here is down with it.** +Every request of a session behind the gate goes to bikoret.ch; if the pod +is not ready (503: stale manna at the day boundary, a Doorpost drift, a +hung rollout) the session cannot reach Anthropic at all. Note that an +`env` entry in `~/.claude/settings.json` OVERRIDES the shell environment +(verified 2026-10-15): `env -u ANTHROPIC_BASE_URL … claude` does nothing +while the gate is armed there. Three tools, all in `scripts/` (install to +`~/.local/bin`), none of which holds a key: + +```bash +claude-direct # start Claude Code straight to Anthropic, whatever + # settings.json says (a --settings JSON on the command + # line outranks every settings file); OAuth as usual +claude-bikoret # start behind the gate; probes the gate first and, if + # it does not answer, says so and starts claude-direct. + # BIKORET_STRICT=1 claude-bikoret refuses instead. +bikoret-settings on|off|status + # arm / disarm the gate for EVERY launcher (desktop, + # background jobs, plain `claude`) via settings.json. + # `on` writes the courtyard key there — the kohen runs + # it, not the builder; `off` is the recovery. +``` + +Order of recovery: `claude-direct` (always works), then `bikoret-settings +off` if background sessions must also recover, then fix the pod +(`curl -s https://bikoret.ch/kodesh/health` names the failed check), then +`bikoret-settings on` and `claude-bikoret` again. + ### The second scroll — *Mishneh haTorah* `/kodesh/health` carries `mesorah_hash` beside `l1_hash`: the mesorah diff --git a/scripts/bikoret-settings b/scripts/bikoret-settings new file mode 100755 index 00000000..a7f2fe53 --- /dev/null +++ b/scripts/bikoret-settings @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Arm or disarm the gate in Claude Code's settings.json (the `env` block is +# applied to EVERY launcher — desktop, background jobs, the plain `claude`). +# +# bikoret-settings on put ANTHROPIC_BASE_URL + ANTHROPIC_CUSTOM_HEADERS in +# bikoret-settings off remove them (the fallback when the gate is down) +# bikoret-settings status say which +# +# `on` writes the courtyard key into a config file, so the transmitter runs it: +# the builder is not allowed to write a credential there. `off` writes none. +import json, os, sys +p = os.path.expanduser("~/.claude/settings.json") +KEYS = ("ANTHROPIC_BASE_URL", "ANTHROPIC_CUSTOM_HEADERS") +mode = sys.argv[1] if len(sys.argv) > 1 else "status" +d = json.load(open(p)) +env = d.get("env", {}) +if mode == "on": + k = [l.split()[1] for l in open(os.path.expanduser("~/.config/shorashim/api_keys")) if l.startswith("courtyard")][0] + env.update({"ANTHROPIC_BASE_URL": "https://bikoret.ch/gate/anthropic", + "ANTHROPIC_CUSTOM_HEADERS": "X-Bikoret-Key: " + k}) + d["env"] = env +elif mode == "off": + for key in KEYS: + env.pop(key, None) + if env: d["env"] = env + else: d.pop("env", None) +elif mode != "status": + sys.exit("usage: bikoret-settings on|off|status") +if mode in ("on", "off"): + json.dump(d, open(p, "w"), indent=2) + os.chmod(p, 0o600) +armed = all(k in d.get("env", {}) for k in KEYS) +print("gate in settings.json:", "ARMED (every launcher goes through bikoret.ch)" if armed else "off (direct to Anthropic)") diff --git a/scripts/claude-bikoret b/scripts/claude-bikoret new file mode 100755 index 00000000..97f5e719 --- /dev/null +++ b/scripts/claude-bikoret @@ -0,0 +1,27 @@ +#!/bin/bash +# Start Claude Code behind the gate (KOHEN "Claude Code behind the gate"). +# The courtyard key is read from the keys file at launch and never written anywhere else. +# +# Fallback (2026-10-15): before launching, probe the gate. If bikoret.ch or +# the pod is down, say so and start DIRECT to Anthropic instead +# (claude-direct), unless BIKORET_STRICT=1 — then refuse, so a session that +# must be examined is never silently unexamined. +GATE="https://bikoret.ch/gate/anthropic" +code=$(curl -sS -m 6 -o /dev/null -w '%{http_code}' -X POST "$GATE/v1/messages" \ + -H 'content-type: application/json' -d '{}' 2>/dev/null) +case "$code" in + 401|400|422) ;; # the gate answered: it is up (it refuses an unkeyed/empty request) + *) + echo "bikoret gate not answering (HTTP ${code:-000}) — see: curl -s https://bikoret.ch/kodesh/health" >&2 + if [ "${BIKORET_STRICT:-0}" = "1" ]; then + echo "BIKORET_STRICT=1: refusing to start unexamined" >&2; exit 1 + fi + echo "starting DIRECT to Anthropic (claude-direct); re-run claude-bikoret when the gate is back" >&2 + exec "$HOME/.local/bin/claude-direct" "$@" + ;; +esac +KEY=$(awk '$1=="courtyard"{print $2}' "$HOME/.config/shorashim/api_keys") +[ -n "$KEY" ] || { echo "no courtyard key in ~/.config/shorashim/api_keys" >&2; exit 1; } +export ANTHROPIC_BASE_URL="$GATE" +export ANTHROPIC_CUSTOM_HEADERS="X-Bikoret-Key: $KEY" +exec claude "$@" diff --git a/scripts/claude-direct b/scripts/claude-direct new file mode 100755 index 00000000..081f2d75 --- /dev/null +++ b/scripts/claude-direct @@ -0,0 +1,12 @@ +#!/bin/bash +# Start Claude Code DIRECT to Anthropic, bypassing the Bikoret gate — the +# fallback for when bikoret.ch / the pod is down (KOHEN "Claude Code behind +# the gate — fallback"). +# +# Why --settings and not `env -u`: an `env` entry in ~/.claude/settings.json +# overrides the shell environment (verified 2026-10-15), so unsetting the +# variables in the shell does nothing while the gate is armed there. A +# `--settings` JSON on the command line outranks every settings file, so it +# wins without touching settings.json. OAuth login is used as normal. +unset ANTHROPIC_BASE_URL ANTHROPIC_CUSTOM_HEADERS +exec claude --settings '{"env":{"ANTHROPIC_BASE_URL":"https://api.anthropic.com","ANTHROPIC_CUSTOM_HEADERS":""}}' "$@"