From 134a99658755451e2b7a71e3076aaf94df31f8d5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 12 Jun 2026 00:32:44 -0400 Subject: [PATCH 1/3] WIP: make tool authoring location rw by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Done with 🤖 --- bin/bwclaude | 35 +++++++++++++ bin/bwcodex | 38 ++++++++++++-- bin/bwcopilot | 44 +++++++++++----- bin/bwopencode | 74 ++++++++++++++++++++++++++- lib/bwrap_sandbox_lib.sh | 108 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 280 insertions(+), 19 deletions(-) diff --git a/bin/bwclaude b/bin/bwclaude index f1d719b..786e34e 100755 --- a/bin/bwclaude +++ b/bin/bwclaude @@ -15,6 +15,8 @@ # - statsig/: bind-mounted read-write (persistent) # - file-history/: bind-mounted read-write (per-file edit history for undo/restore) # - sessions/: bind-mounted read-write (session metadata for --continue) +# - skills/, agents/, commands/: bind-mounted read-write, always created +# on host (user-authored definitions; persist across sessions) # - history.jsonl: bind-mounted read-write (prompt history across sessions) # - .last-cleanup: bind-mounted read-write (throttles Claude's cleanup runs) # - settings.json: bind-mounted read-write (if it exists; --init-auth creates a stub) @@ -227,6 +229,21 @@ resolve_claude_paths() { CLAUDE_CREDENTIALS_FILE="${CLAUDE_HOME_DIR}/.credentials.json" + # Authoring directories under ~/.claude/ that hold user-created + # markdown definitions. Mounted read-write (see + # build_claude_authoring_mounts) so they can be created and edited + # from inside the sandbox and persist on the host. + # + # Each kind's primary (Claude-native) location is force-created so + # authoring works out of the box. Claude has a single global search + # location per kind, so there are no alternates. + # - skills/ : SKILL.md skill definitions + # - agents/ : custom agent markdown profiles + # - commands/ : custom slash-command markdown templates + CLAUDE_SKILLS_PRIMARY="${CLAUDE_HOME_DIR}/skills" + CLAUDE_AGENTS_PRIMARY="${CLAUDE_HOME_DIR}/agents" + CLAUDE_COMMANDS_PRIMARY="${CLAUDE_HOME_DIR}/commands" + # State file at $HOME/.claude.json (not inside .claude/) tracks # hasCompletedOnboarding, numStartups, userID, project history. CLAUDE_STATE_FILE="${_HOME}/.claude.json" @@ -250,6 +267,11 @@ ensure_host_dirs() { "${CLAUDE_HOME_DIR}/sessions" touch "${CLAUDE_HOME_DIR}/history.jsonl" \ "${CLAUDE_HOME_DIR}/.last-cleanup" + + # Create user-writable authoring directories so a read-write bind + # always has a real target on the host (a tmpfs would silently + # discard a user's first skill/agent/command). + mkdir -p "${CLAUDE_SKILLS_PRIMARY}" "${CLAUDE_AGENTS_PRIMARY}" "${CLAUDE_COMMANDS_PRIMARY}" } # ── Claude home (ephemeral tmpfs + selective persistence) ──────── @@ -337,6 +359,18 @@ build_claude_cache_mount() { BWRAP_ARGS+=(--bind "${CLAUDE_CACHE_DIR}" "${CLAUDE_CACHE_DIR}") } +# ── Claude authoring dirs (read-write, persistent) ────────────── +# Mount skills/, agents/, commands/ (and any present fallback dirs) +# read-write so users can author reusable definitions from inside the +# sandbox and have them persist on the host. Must run AFTER +# build_claude_home_mount (which creates the writable ${CLAUDE_HOME_DIR} +# tmpfs) so these binds land on top of it. +build_claude_authoring_mounts() { + build_authoring_kind "claude skills dir" "${CLAUDE_SKILLS_PRIMARY}" + build_authoring_kind "claude agents dir" "${CLAUDE_AGENTS_PRIMARY}" + build_authoring_kind "claude commands dir" "${CLAUDE_COMMANDS_PRIMARY}" +} + # ── Claude env vars ────────────────────────────────────────────── build_claude_env() { # Foundry mode is forced on (see CLAUDE_CODE_USE_FOUNDRY below). @@ -448,6 +482,7 @@ main() { build_workdir_mount build_claude_home_mount build_claude_cache_mount + build_claude_authoring_mounts build_git_mounts build_pixi_mounts build_ccache_mounts diff --git a/bin/bwcodex b/bin/bwcodex index dc79b09..8af4d92 100755 --- a/bin/bwcodex +++ b/bin/bwcodex @@ -38,7 +38,8 @@ # - /etc/codex/requirements.toml: admin-enforced restrictions (if # present); bind-mounted read-only. Users cannot loosen these. # - AGENTS.md: bind-mounted read-only (if it exists) -# - skills/: bind-mounted read-only (if it exists) +# - skills/, prompts/: bind-mounted read-write, always created on host +# (user-authored definitions; persist across sessions) # - packages/: bind-mounted read-only (if it exists, installer cache) # - auth.json: bind-mounted read-write only if it already exists on host; # --init-auth creates a {} stub for first-time ChatGPT-login setup @@ -260,10 +261,20 @@ resolve_codex_paths() { CODEX_AUTH_FILE="${CODEX_HOME_DIR}/auth.json" CODEX_SESSIONS_DIR="${CODEX_HOME_DIR}/sessions" CODEX_LOG_DIR="${CODEX_HOME_DIR}/log" - CODEX_SKILLS_DIR="${CODEX_HOME_DIR}/skills" CODEX_HISTORY_FILE="${CODEX_HOME_DIR}/history.jsonl" CODEX_PACKAGES_DIR="${CODEX_HOME_DIR}/packages" + # Authoring directories under ~/.codex/ that hold user-created + # markdown definitions. Mounted read-write (see + # build_codex_authoring_mounts) so they can be created and edited + # from inside the sandbox and persist on the host. Each kind's + # primary (Codex-native) location is force-created so authoring works + # out of the box. + # - skills/ : SKILL.md skill definitions + # - prompts/ : custom prompt markdown templates (Codex slash prompts) + CODEX_SKILLS_PRIMARY="${CODEX_HOME_DIR}/skills" + CODEX_PROMPTS_PRIMARY="${CODEX_HOME_DIR}/prompts" + # Validate the user-controllable root directory once here so that all # sub-path mounts derived from it are implicitly covered. _check_dir_safe "${CODEX_HOME_DIR}" "CODEX_HOME" @@ -275,6 +286,11 @@ ensure_host_dirs() { "${CODEX_SESSIONS_DIR}" \ "${CODEX_LOG_DIR}" touch "${CODEX_HISTORY_FILE}" + + # Create user-writable authoring directories so a read-write bind + # always has a real target on the host (a tmpfs would silently + # discard a user's first skill/prompt). + mkdir -p "${CODEX_SKILLS_PRIMARY}" "${CODEX_PROMPTS_PRIMARY}" } # Fail fast if no config.toml exists. Codex requires a config.toml to @@ -367,9 +383,10 @@ build_codex_home_mount() { BWRAP_ARGS+=(--ro-bind "${CODEX_AGENTS_FILE}" "${CODEX_AGENTS_FILE}") fi - if [[ -d "${CODEX_SKILLS_DIR}" ]]; then - BWRAP_ARGS+=(--ro-bind "${CODEX_SKILLS_DIR}" "${CODEX_SKILLS_DIR}") - fi + # skills/ and other authoring directories are mounted read-write by + # build_codex_authoring_mounts (called from main after this function) + # so users can author skills/prompts from inside the sandbox and have + # them persist on the host. if [[ -d "${CODEX_PACKAGES_DIR}" ]]; then BWRAP_ARGS+=(--ro-bind "${CODEX_PACKAGES_DIR}" "${CODEX_PACKAGES_DIR}") @@ -386,6 +403,16 @@ build_codex_home_mount() { fi } +# ── Codex authoring dirs (read-write, persistent) ─────────────── +# Mount skills/ and prompts/ read-write so users can author reusable +# definitions from inside the sandbox and have them persist on the host. +# Must run AFTER build_codex_home_mount (which creates the writable +# ${CODEX_HOME_DIR} tmpfs) so these binds land on top of it. +build_codex_authoring_mounts() { + build_authoring_kind "codex skills dir" "${CODEX_SKILLS_PRIMARY}" + build_authoring_kind "codex prompts dir" "${CODEX_PROMPTS_PRIMARY}" +} + # ── Codex env vars ─────────────────────────────────────────────── # Pass through AIFAPIM_* (Hermes routing) and the documented Codex # env vars; default OPENAI_API_KEY to "dummy" if unset so the Codex @@ -470,6 +497,7 @@ main() { build_home_tmpfs build_workdir_mount build_codex_home_mount + build_codex_authoring_mounts build_git_mounts build_pixi_mounts build_ccache_mounts diff --git a/bin/bwcopilot b/bin/bwcopilot index 89941da..5c90e18 100755 --- a/bin/bwcopilot +++ b/bin/bwcopilot @@ -11,8 +11,8 @@ # - Copilot home (~/.copilot or $COPILOT_HOME): tmpfs (ephemeral by default) # - session-state/: bind-mounted read-write (persistent) # - logs/: bind-mounted read-write (persistent) -# - skills/: bind-mounted read-write (user-created skill definitions) -# - agents/: bind-mounted read-write (user-created custom agents) +# - skills/, agents/: bind-mounted read-write, always created on host +# (user-authored definitions; persist across sessions) # - installed-plugins/: bind-mounted read-only (prevents agent from installing plugins) # - config.json: COPIED into tmpfs (host auth available, changes ephemeral) # - Use --init-auth to make config.json persistent (for personal machines) @@ -231,6 +231,17 @@ resolve_copilot_paths() { COPILOT_SESSION_DIR="${COPILOT_HOME_DIR}/session-state" COPILOT_LOGS_DIR="${COPILOT_HOME_DIR}/logs" + # Authoring directories under ~/.copilot/ that hold user-created + # markdown definitions. Mounted read-write (see + # build_copilot_authoring_mounts) so they can be created and edited + # from inside the sandbox and persist on the host. Each kind's + # primary (Copilot-native) location is force-created so authoring + # works out of the box. + # - skills/ : SKILL.md skill definitions + # - agents/ : custom agent markdown profiles + COPILOT_SKILLS_PRIMARY="${COPILOT_HOME_DIR}/skills" + COPILOT_AGENTS_PRIMARY="${COPILOT_HOME_DIR}/agents" + # Validate the user-controllable root directories once here so that all # sub-path mounts derived from them are implicitly covered. _check_dir_safe "${COPILOT_HOME_DIR}" "COPILOT_HOME" @@ -242,8 +253,10 @@ ensure_host_dirs() { mkdir -p "${COPILOT_HOME_DIR}" "${COPILOT_CACHE_DIR}" # Create persistent subdirectories for bind-mounts mkdir -p "${COPILOT_SESSION_DIR}" "${COPILOT_LOGS_DIR}" - # Create user-writable customization directories - mkdir -p "${COPILOT_HOME_DIR}/skills" "${COPILOT_HOME_DIR}/agents" + # Create user-writable authoring directories so a read-write bind + # always has a real target on the host (a tmpfs would silently + # discard a user's first skill/agent). + mkdir -p "${COPILOT_SKILLS_PRIMARY}" "${COPILOT_AGENTS_PRIMARY}" } # ── Copilot home (ephemeral tmpfs + selective persistence) ────── @@ -301,14 +314,10 @@ build_copilot_home_mount() { fi done - # Bind-mount user-writable customization directories (read-write) - # Skills and agents are user-created files, not installed packages. - for _cfg in skills agents; do - local _path="${COPILOT_HOME_DIR}/${_cfg}" - if [[ -e "${_path}" ]]; then - BWRAP_ARGS+=(--bind "${_path}" "${_path}") - fi - done + # User-writable authoring directories (skills/, agents/, ...) are + # mounted read-write by build_copilot_authoring_mounts (called from + # main after this function) so they are symlink-resolved, sanitized, + # and created on the host when missing. # Handle config.json: copy or bind depending on --init-auth if [[ "${INIT_AUTH}" -eq 1 ]]; then @@ -328,6 +337,16 @@ build_copilot_home_mount() { fi } +# ── Copilot authoring dirs (read-write, persistent) ───────────── +# Mount skills/ and agents/ read-write so users can author reusable +# definitions from inside the sandbox and have them persist on the host. +# Must run AFTER build_copilot_home_mount (which creates the writable +# ${COPILOT_HOME_DIR} tmpfs) so these binds land on top of it. +build_copilot_authoring_mounts() { + build_authoring_kind "copilot skills dir" "${COPILOT_SKILLS_PRIMARY}" + build_authoring_kind "copilot agents dir" "${COPILOT_AGENTS_PRIMARY}" +} + # ── Copilot cache (read-write) ────────────────────────────────── build_copilot_cache_mount() { BWRAP_ARGS+=(--bind "${COPILOT_CACHE_DIR}" "${COPILOT_CACHE_DIR}") @@ -406,6 +425,7 @@ main() { build_home_tmpfs build_workdir_mount build_copilot_home_mount + build_copilot_authoring_mounts build_copilot_cache_mount build_gh_auth_mount build_git_mounts diff --git a/bin/bwopencode b/bin/bwopencode index ff80b16..31fa6b8 100755 --- a/bin/bwopencode +++ b/bin/bwopencode @@ -8,7 +8,19 @@ # # The sandbox provides filesystem isolation: # - Current directory: read-write -# - OpenCode config: read-only (symlinks resolved) +# - OpenCode config: read-only (symlinks resolved), EXCEPT the +# authoring directories below, which are read-write and persistent +# - OpenCode authoring dirs (read-write, persistent on host): +# For each kind the opencode-native primary location is always +# created so users can author from inside the sandbox out of the box: +# skills -> ~/.config/opencode/skills/ (primary) +# agents -> ~/.config/opencode/agents/ (primary) +# commands -> ~/.config/opencode/commands/ (primary) +# Additional search locations for the SAME kind are mounted rw only +# if they already exist on the host: +# skills -> ~/.claude/skills/, ~/.agents/skills/ +# agents -> ~/.claude/agents/, ~/.agents/agents/ +# commands -> ~/.claude/commands/ # - OpenCode data dir: ephemeral tmpfs with selective persistence # - auth.json: persisted only if it already exists on host; # otherwise created in tmpfs (discarded on exit) @@ -196,6 +208,30 @@ resolve_opencode_paths() { OC_CACHE_DIR="${XDG_CACHE_HOME}/opencode" OC_AUTH_FILE="${OC_DATA_DIR}/auth.json" + # Authoring directories that hold user-created markdown definitions. + # These are mounted read-write (see build_opencode_authoring_mounts) + # so they can be created and edited from inside the sandbox and + # persist on the host. + # + # opencode searches several locations per kind; the primary + # (opencode-native) location is force-created so authoring works out + # of the box, while the Claude-/agent-compatible search locations are + # mounted read-write only if they already exist on the host. + # + # The opencode-native primaries (under ${OC_CONFIG_DIR}) are excluded + # from the read-only config bind in resolve_config_symlinks so the + # read-write bind is not shadowed. + OC_SKILLS_PRIMARY="${OC_CONFIG_DIR}/skills" + OC_SKILLS_ALT=("${_HOME}/.claude/skills" "${_HOME}/.agents/skills") + OC_AGENTS_PRIMARY="${OC_CONFIG_DIR}/agents" + OC_AGENTS_ALT=("${_HOME}/.claude/agents" "${_HOME}/.agents/agents") + OC_COMMANDS_PRIMARY="${OC_CONFIG_DIR}/commands" + OC_COMMANDS_ALT=("${_HOME}/.claude/commands") + + # Basenames under ${OC_CONFIG_DIR} that resolve_config_symlinks must + # skip (they are handled read-write below). + OC_CONFIG_AUTHORING_SKIP="skills agents commands" + # Validate the user-controllable root directories once here so that all # sub-path mounts derived from them are implicitly covered. # XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_CACHE_HOME are already checked @@ -222,6 +258,14 @@ ensure_host_dirs() { touch "${OC_DATA_DIR}/opencode.db" \ "${OC_DATA_DIR}/opencode.db-shm" \ "${OC_DATA_DIR}/opencode.db-wal" + + # Authoring directories: create the primary location for each kind on + # the host so a read-write bind always has a real target. Without + # this, the parent ~/.config/opencode/ is a tmpfs inside the sandbox + # and a user's first skill/agent/command would be silently discarded + # on exit. Alternate search locations are NOT created here — they are + # only mounted read-write if they already exist. + mkdir -p "${OC_SKILLS_PRIMARY}" "${OC_AGENTS_PRIMARY}" "${OC_COMMANDS_PRIMARY}" } # Resolve opencode config symlinks. @@ -237,9 +281,17 @@ resolve_config_symlinks() { OC_CONFIG_BINDS=() [[ -d "${OC_CONFIG_DIR}" ]] || return 0 - local entry local_resolved + local entry base local_resolved for entry in "${OC_CONFIG_DIR}"/*; do [[ -e "${entry}" ]] || continue + # Skip authoring directories — they are mounted read-write by + # build_opencode_authoring_mounts so users can create/edit + # skills, agents, and commands from inside the sandbox. Binding + # them read-only here would shadow that read-write bind. + base="${entry##*/}" + case " ${OC_CONFIG_AUTHORING_SKIP} " in + *" ${base} "*) continue ;; + esac if [[ -L "${entry}" ]]; then local_resolved="$(readlink -f "${entry}")" else @@ -315,6 +367,23 @@ build_opencode_config_mount() { fi } +# ── OpenCode authoring dirs (read-write, persistent) ──────────── +# Mount the skills/agents/commands search locations read-write so users +# can author reusable definitions from inside the sandbox and have them +# persist on the host. For each kind, the opencode-native location is +# created and mounted; the Claude-/agent-compatible search locations are +# mounted read-write only if they already exist. Must run AFTER +# build_opencode_config_mount (which creates the writable ${OC_CONFIG_DIR} +# --dir) so these binds land on top of it. +build_opencode_authoring_mounts() { + build_authoring_kind "opencode skills dir" \ + "${OC_SKILLS_PRIMARY}" "${OC_SKILLS_ALT[@]}" + build_authoring_kind "opencode agents dir" \ + "${OC_AGENTS_PRIMARY}" "${OC_AGENTS_ALT[@]}" + build_authoring_kind "opencode commands dir" \ + "${OC_COMMANDS_PRIMARY}" "${OC_COMMANDS_ALT[@]}" +} + # ── OpenCode data (ephemeral tmpfs + selective persistence) ────── # Mount the data dir as ephemeral tmpfs, then selectively bind-mount # the files and directories that must persist across sessions. @@ -412,6 +481,7 @@ main() { build_workdir_mount build_persistent_dotdir build_opencode_config_mount + build_opencode_authoring_mounts build_opencode_data_mounts build_opencode_cache_mount build_git_mounts diff --git a/lib/bwrap_sandbox_lib.sh b/lib/bwrap_sandbox_lib.sh index 2653ce9..5e1e14e 100644 --- a/lib/bwrap_sandbox_lib.sh +++ b/lib/bwrap_sandbox_lib.sh @@ -586,6 +586,114 @@ build_extra_path_mounts() { done } +# ── Authoring directories (read-write, persistent) ─────────────── +# +# Tools (opencode, claude, copilot, codex) let users author reusable +# definitions — skills, agents, commands/prompts — as plain markdown +# files under their config/home directory. For these to be created +# and edited *from inside the sandbox* and survive across sessions, +# the directories must be bind-mounted read-write from the real host +# location (a tmpfs would silently discard a user's first skill). +# +# A single *kind* of definition (e.g. "skills") may be searched for in +# several locations by the tool — for opencode, skills are discovered +# in ~/.config/opencode/skills/, ~/.claude/skills/, and ~/.agents/skills/. +# Within one kind, exactly the PRIMARY location is force-created so that +# authoring works out of the box; every other search location for that +# kind is mounted read-write only if it already exists on the host (we +# do not manufacture empty alternate trees the tool may not expect). +# +# _bind_authoring_dir DIR CREATE CONTEXT +# Resolve, sanitize, and read-write bind-mount the authoring directory +# DIR (an absolute path). +# +# DIR : absolute path to the authoring directory. +# CREATE : "create" -> mkdir -p the host dir first so first-time +# authoring works out of the box; "if-exists" -> only +# mount when the directory already exists on the host. +# CONTEXT : label for safety-check error messages. +# +# The target path is canonicalized with readlink -f (resolving any +# symlink, e.g. skills/ pointing into a dotfiles repo) and passed +# through _check_path_safe before the bind is emitted — identical +# treatment to --rw-path. Intermediate --dir entries are created +# for paths under $HOME so the bind mount point exists in the tmpfs. +# +# Idempotent: a path already registered in _MOUNTED_PREFIXES (or a +# parent of it) is skipped so we never emit a duplicate bind. +# +# Must be called AFTER build_home_tmpfs (for intermediate --dir +# creation) and AFTER the tool's own home/config tmpfs+mounts so the +# read-write bind lands on top of the writable parent --dir rather +# than being shadowed by a broader read-only bind. +_bind_authoring_dir() { + local dir="$1" create="$2" context="$3" + + if [[ "${create}" == "create" ]]; then + mkdir -p "${dir}" + elif [[ ! -e "${dir}" ]]; then + return 0 + fi + + # Resolve symlinks so we bind the real target, then sanitize it. + local canon + canon="$(readlink -f "${dir}" 2> /dev/null || echo "${dir}")" + _check_path_safe "${canon}" "${context}" + + # Skip if this path (or a parent tree) is already mounted. + local check="${canon}" + while [[ "${check}" != "/" ]]; do + if [[ -n "${_MOUNTED_PREFIXES["${check}"]:-}" ]]; then + return 0 + fi + check="${check%/*}" + [[ -z "${check}" ]] && check="/" + done + + # Create intermediate --dir entries for paths under $HOME so the + # bind mount point exists inside the tmpfs. + if [[ "${canon}" == "${_HOME}"/* ]]; then + local _rel="${canon#"${_HOME}"/}" + local _accum="${_HOME}" + local _parts _i + IFS='/' read -ra _parts <<< "${_rel}" + for ((_i = 0; _i < ${#_parts[@]} - 1; _i++)); do + _accum="${_accum}/${_parts[$_i]}" + BWRAP_ARGS+=(--dir "${_accum}") + done + fi + + BWRAP_ARGS+=(--bind "${canon}" "${canon}") + _MOUNTED_PREFIXES["${canon}"]=1 +} + +# build_authoring_kind CONTEXT_LABEL PRIMARY [ALT_SEARCH_DIR ...] +# Read-write bind-mount every search location for ONE kind of authored +# definition (skills, agents, commands, ...) so the user can create and +# edit them from inside the sandbox with persistence. +# +# CONTEXT_LABEL : label for safety-check error messages (e.g. +# "opencode skills dir"). +# PRIMARY : the canonical/primary search location for this kind. +# Always created (mkdir -p) and mounted read-write so +# authoring works out of the box on a fresh install. +# ALT_SEARCH_DIR : zero or more additional search locations the tool +# consults for this SAME kind. Each is mounted +# read-write only if it already exists on the host. +# +# All paths are absolute, symlink-resolved, and sanitized by +# _bind_authoring_dir. +build_authoring_kind() { + local context_label="$1" + local primary="$2" + shift 2 + _bind_authoring_dir "${primary}" "create" "${context_label} (primary)" + local _alt + for _alt in "$@"; do + _bind_authoring_dir "${_alt}" "if-exists" "${context_label} (alternate)" + done +} + # ── Path resolution ────────────────────────────────────────────── resolve_common_paths() { From 027ceff1678115ff1ed12a73dbb2e1871467d57a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 12 Jun 2026 16:11:32 -0400 Subject: [PATCH 2/3] MNT: drop reference to hermes --- bin/bwclaude | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/bwclaude b/bin/bwclaude index 786e34e..886d0b6 100755 --- a/bin/bwclaude +++ b/bin/bwclaude @@ -386,7 +386,7 @@ build_claude_env() { echo "Error: bwclaude (foundry mode) requires an API key." >&2 echo "Export one of:" >&2 echo " ANTHROPIC_FOUNDRY_API_KEY= # direct foundry key" >&2 - echo " AIFAPIM_API_KEY= # NSLS-II hermes/AIFAPIM key (preferred)" >&2 + echo " AIFAPIM_API_KEY= # NSLS-II AIFAPIM key (preferred)" >&2 exit 1 fi @@ -407,7 +407,7 @@ build_claude_env() { else echo "Error: bwclaude requires AIFAPIM_HOST (or ANTHROPIC_FOUNDRY_BASE_URL) to be set." >&2 echo "Export one of:" >&2 - echo " AIFAPIM_HOST=hermes.nsls2.bnl.gov # NSLS-II Hermes gateway hostname" >&2 + echo " AIFAPIM_HOST= # NSLS-II AIFAPIM gateway hostname" >&2 echo " ANTHROPIC_FOUNDRY_BASE_URL=https://... # full Foundry base URL override" >&2 exit 1 fi From d76fa0689e653ec12103bd4550c5ab7b19f55983 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 12 Jun 2026 16:26:46 -0400 Subject: [PATCH 3/3] ENH: make sure we route "small" tasks to right model --- bin/bwclaude | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/bwclaude b/bin/bwclaude index 886d0b6..29dab6b 100755 --- a/bin/bwclaude +++ b/bin/bwclaude @@ -424,6 +424,12 @@ build_claude_env() { BWRAP_ARGS+=(--setenv CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS 1) BWRAP_ARGS+=(--setenv CLAUDE_CODE_USE_FOUNDRY 1) + # In foundry mode the haiku model used for background tasks defaults to a + # date-pinned ID that the foundry proxy may not recognise. Override with + # the un-pinned alias unless the caller has already set a preference. + BWRAP_ARGS+=(--setenv ANTHROPIC_DEFAULT_HAIKU_MODEL \ + "${ANTHROPIC_DEFAULT_HAIKU_MODEL:-claude-haiku-4-5}") + # Debug/diagnostic env vars — passed through if set on the host. pass_through_if_set ANTHROPIC_LOG pass_through_if_set NODE_DEBUG