Compare commits

..
Author SHA1 Message Date
Scott KarpandClaude Fable 5.1 07463c9c37 kohen: claude-key — Claude Code on the API key when the login is what failed
2026-10-15 21:08Z: the daemon's proactive OAuth refresh failed (no
incident at Anthropic; most likely a cross-process race on the
single-use refresh token), the credential was dropped, and every
session on the VM showed "Login expired · Please run /login" until a
browser login took at 23:50Z — two "Login interrupted" first. The gate
was off; the fallback of 4acd56c4 could not help, because it is the
same OAuth credential.

scripts/claude-key starts Claude Code on the Anthropic API key from
~/.config/shorashim/upstream_keys (read at launch, never written),
direct to Anthropic whatever settings.json says. Proven:
`claude-key auth status` → authMethod api_key, apiKeySource
ANTHROPIC_API_KEY; a -p run from a directory with a dead gate armed in
project settings answered. KOHEN.md: the login recovery (claude-key,
`claude auth login` from the shell, `auth status --text`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 23:55:48 +00:00
Scott KarpandClaude Fable 5.1 4acd56c4b6 kohen: fallback for Claude Code when the gate is down
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 <noreply@anthropic.com>
2026-10-15 19:12:04 +00:00
5 changed files with 138 additions and 0 deletions

No files matched your search

+50
View File
@@ -620,6 +620,56 @@ 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-key # the same, on the Anthropic API KEY from
# ~/.config/shorashim/upstream_keys instead of the
# subscription's OAuth token: no browser, no /login.
# Billed to the API console account, not the Max plan.
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 while the OAuth token
is good), 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.
**When it is the login, not the gate.** Every session on the VM shares
one OAuth credential (`~/.claude/.credentials.json`); the access token
lives eight hours and the daemon refreshes it ahead of time. On
2026-10-15 21:08Z that refresh failed with no incident at Anthropic
(most likely two processes racing on the single-use refresh token), the
credential was dropped, and every session said "Login expired · Please
run /login" for 2 h 43 min. The browser flow from a remote shell is
fragile ("Login interrupted" twice before it took). Recovery:
```bash
claude-key # work NOW on the API key; nothing to log in to
claude auth login # from the shell, not inside a session: prints the
# URL, paste the code it gives back; `--console`
# signs in to the API account instead of the plan
claude auth status --text # says which credential a launch would use
```
### The second scroll — *Mishneh haTorah*
`/kodesh/health` carries `mesorah_hash` beside `l1_hash`: the mesorah
+33
View File
@@ -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)")
+27
View File
@@ -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 "$@"
+12
View File
@@ -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":""}}' "$@"
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# Start Claude Code on the Anthropic API KEY instead of the subscription's
# OAuth token — the second leg of the fallback (KOHEN "Claude Code behind
# the gate — fallback"). For when /login fails or the OAuth refresh died
# (2026-10-15 21:08Z: the daemon's proactive refresh failed, every session
# on the VM showed "Login expired", and the browser flow took three tries
# from a remote shell).
#
# Billing: the API console account (pay per token), not the Max plan.
# The key is read from the upstream keys file at launch and never written
# anywhere. Direct to Anthropic, whatever settings.json says.
KEY=$(awk '$1=="anthropic"{print $2}' "$HOME/.config/shorashim/upstream_keys")
[ -n "$KEY" ] || { echo "no 'anthropic <key>' line in ~/.config/shorashim/upstream_keys" >&2; exit 1; }
export ANTHROPIC_API_KEY="$KEY"
unset ANTHROPIC_BASE_URL ANTHROPIC_CUSTOM_HEADERS
exec claude --settings '{"env":{"ANTHROPIC_BASE_URL":"https://api.anthropic.com","ANTHROPIC_CUSTOM_HEADERS":""}}' "$@"