#!/bin/bash # Post-create setup for the Authoring devcontainer. set -euo pipefail # Install every harness a worker can author with, and point each at the LLM proxy. # Driven by scripts/harness-registry.toml, so adding a harness is a registry entry # rather than an edit here and in the sibling container's post-create. set -a; . /workspace/.env 2>/dev/null || true; set +a . /workspace/scripts/setup-harnesses.sh harness_setup_all npm install git config --global --add safe.directory '*' # --- Claude auth for non-interactive / background-agent sessions ------------ # Interactive shells source .env via .bashrc (below), so `claude` picks up a # live, per-launch ANTHROPIC_API_KEY. But sessions that don't run a login # shell (headless `claude -p`, background agents) never source .env and have # no key. apiKeyHelper closes that gap: Claude Code runs this script to fetch # the key, re-reading the live .env every time (fresh per session, re-checked # on the TTL below), so a rotated key is picked up with no container rebuild. # # Precedence is cloud > ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY (env) > # apiKeyHelper > OAuth. Interactive shells still have ANTHROPIC_API_KEY in # their env (from .bashrc), so it outranks the helper there — also live, so # fine. We deliberately do NOT put ANTHROPIC_API_KEY in the settings `env` # block: that would cache it at daemon start and shadow the helper, defeating # the whole point. # # The base URL does NOT rotate per task, so it doesn't need the live-helper # treatment — but background sessions still need it (they never source .env). # So we read it from .env ONCE here and bake it into the settings `env` block. # .env stays the single source of truth (no hardcoded copy to keep in sync on # a proxy-domain change), and the baked value is the worker's own .env value. # Caveat: it's a snapshot — changing ANTHROPIC_BASE_URL in .env after boot # needs a container rebuild to take effect (the key, which rotates, stays live). mkdir -p /root/.claude cat > /root/.claude/anthropic-key-helper.sh <<'HELPER' #!/bin/bash set -a; . /workspace/.env 2>/dev/null || true; set +a printf '%s' "${ANTHROPIC_API_KEY:-}" HELPER chmod +x /root/.claude/anthropic-key-helper.sh # Derive the base URL from .env (empty if absent -> line omitted, graceful). AUTH_BASE_URL=$(set -a; . /workspace/.env 2>/dev/null || true; set +a; printf '%s' "${ANTHROPIC_BASE_URL:-}") # Write valid JSON via node (guaranteed present: node base image); only include # the base-URL key when .env actually had one. AUTH_BASE_URL="$AUTH_BASE_URL" node -e ' const fs = require("fs"); const env = { CLAUDE_CODE_API_KEY_HELPER_TTL_MS: "60000", CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" }; if (process.env.AUTH_BASE_URL) env.ANTHROPIC_BASE_URL = process.env.AUTH_BASE_URL; fs.writeFileSync( "/root/.claude/settings.json", JSON.stringify({ apiKeyHelper: "/root/.claude/anthropic-key-helper.sh", env }, null, 2) + "\n" ); ' # Reference-data corpus: expose it at the stable /data/zeta-corpus path (the same path a trial # uses) by symlinking to the toolkit's bind-mounted copy. No-op if this toolkit ships no corpus. if [ -d /workspace/data/zeta-corpus ]; then { mkdir -p /data || sudo mkdir -p /data; } 2>/dev/null || true { ln -sfn /workspace/data/zeta-corpus /data/zeta-corpus \ || sudo ln -sfn /workspace/data/zeta-corpus /data/zeta-corpus; } 2>/dev/null || true fi # Shell setup cat >> ~/.bashrc <<'BASHRC' test -f .env && set -a && source .env && set +a # Interactive shells only below. An agent's shell tool sources .bashrc too, so without # this the welcome banner prints into command output and container_start fires per # command rather than per session. case $- in *i*) ;; *) return ;; esac # Hide ANTHROPIC_API_KEY from the `claude` process so it uses the (live) # apiKeyHelper as its single credential source — same key, read from .env every # call. Without this, an interactive shell has BOTH the env key AND the helper # set, and Claude Code prints a scary "auth may not work as expected" warning # (auth still works — the env key wins — but the warning alarms workers). The # key stays in the shell env for harbor etc.; only `claude` runs without it. # These pin the ASSISTANT's model and effort, not the agent-under-test's, so they don't # track the registry: here we want the strongest available model, a trial wants a pinned id. alias claude="env -u ANTHROPIC_API_KEY claude --model opus[1m] --effort max" alias codex="codex --model gpt-5.6-sol -c model_reasoning_effort=max" export PS1="\[\033[1;33m\][raccoon-authoring]\[\033[0m\] \w\$ " bash scripts/welcome.sh authoring 2>/dev/null _AK="fde503c3bdb6e5cc9c48b1f8e4c2abeb" _DK="e966e45af5ad1a18005f9fdb831186ea" _WID="w-msklydwj-cpsz" _VER="17ed6f400" _CT="authoring" _RP=$(node -e "try{process.stdout.write(require('$PWD/toolkit.json').repo)}catch{}" 2>/dev/null) _SID="$(date +%s)-$$" _LAT=0 _ev() { [ -z "$_AK" ] && return { curl -s -X POST "https://api2.amplitude.com/2/httpapi" \ -H "Content-Type: application/json" \ -d "{\"api_key\":\"$_AK\",\"events\":[{\"user_id\":\"$_WID\",\"event_type\":\"raccoon.$1\",\"event_properties\":{\"product\":\"raccoon\",\"container\":\"$_CT\",\"repo\":\"$_RP\",\"toolkit_version\":\"$_VER\",\"session_id\":\"$_SID\"},\"session_id\":$(date +%s000)}]}" \ >/dev/null 2>&1 & } 2>/dev/null; disown 2>/dev/null } _dl() { [ -z "$_DK" ] && return { curl -s -X POST "https://http-intake.logs.datadoghq.com/api/v2/logs" \ -H "DD-API-KEY: $_DK" -H "Content-Type: application/json" \ -d "[{\"ddsource\":\"raccoon\",\"service\":\"toolkit\",\"hostname\":\"$(hostname)\",\"status\":\"$1\",\"message\":\"$2\",\"ddtags\":\"container:$_CT,worker:$_WID,repo:$_RP,toolkit_version:$_VER\"}]" \ >/dev/null 2>&1 & } 2>/dev/null; disown 2>/dev/null } _pc() { local n; n=$(date +%s); if (( n - _LAT >= 300 )); then _LAT=$n; _ev active; fi; } PROMPT_COMMAND="_pc;${PROMPT_COMMAND:-}" trap '_ev container_stop; _dl info container_stop; wait' EXIT _ev container_start _dl info container_start BASHRC