From 073011c8c6c8a33281e63ced6d979352e156d070 Mon Sep 17 00:00:00 2001 From: Kadriya Bazarova Date: Tue, 7 Jul 2026 23:26:25 -0500 Subject: [PATCH 1/9] fix(backends): pass FIREWORKS_API_KEY through to opencode cmux spawns cmux workspaces get none of firstmate's own process environment, so an opencode crewmate spawned via the cmux backend never saw FIREWORKS_API_KEY and its Fireworks-backed models (e.g. GLM 5.2) failed outright. fm_backend_cmux_create_task now takes the resolved harness, fetches the key from AWS Secrets Manager (opencode/fireworks-api-key, us-east-1) using whatever AWS profile is ambient, and passes it to `cmux new-workspace` as --env FIREWORKS_API_KEY= - the flag cmux-control's own cmuxctl already uses for this. The secret only ever lands in a shell variable, never printed or logged. Fixed along the way: an empty-array expansion under `set -u` that bash 3.2 treats as an unbound-variable error, caught by the real-cmux smoke test. --- bin/backends/cmux.sh | 72 +++++++++++++++++-- bin/fm-spawn.sh | 2 +- docs/cmux-backend.md | 11 +++ tests/fm-backend-cmux.test.sh | 130 ++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 6 deletions(-) diff --git a/bin/backends/cmux.sh b/bin/backends/cmux.sh index 69dc0b53b..56a44b1ed 100644 --- a/bin/backends/cmux.sh +++ b/bin/backends/cmux.sh @@ -341,6 +341,57 @@ fm_backend_cmux_surface_id_for_workspace() { # | jq -r '.panes[0] // {} | .selected_surface_id // (.surface_ids[0] // empty)' 2>/dev/null } +# fm_backend_cmux_secret_specs_for_harness: zero or more "ENV_VAR +# secret-name" lines naming the provider credentials that must be explicitly +# injected into a cmux workspace for . Needed only for cmux: unlike +# tmux/herdr/zellij (which fork a task inside the same host shell and so +# inherit firstmate's own process environment), a cmux terminal surface's +# environment is heavily scrubbed at spawn time (docs/cmux-backend.md +# "Provider credential passthrough (--env)") - ambient provider API keys +# never reach it. One pairing exists today: opencode's Fireworks-backed +# models (e.g. GLM 5.2) need FIREWORKS_API_KEY. Expressed as a lookup table +# so a second pairing is a one-line addition rather than a new framework. +fm_backend_cmux_secret_specs_for_harness() { # + case "$1" in + opencode) printf 'FIREWORKS_API_KEY opencode/fireworks-api-key\n' ;; + esac +} + +# fm_backend_cmux_fetch_secret: print 's current value from AWS +# Secrets Manager (region us-east-1) on stdout, using whatever AWS +# credentials/profile are already ambient in this process's environment - +# never a hardcoded profile. Fails loudly rather than continuing without the +# key: a missing 'aws' CLI, a failed call (including no credentials +# configured), or an empty secret are all refused. +fm_backend_cmux_fetch_secret() { # + local secret_name=$1 value + command -v aws >/dev/null 2>&1 || { echo "error: cmux env passthrough needs secret '$secret_name' but the 'aws' CLI is not installed" >&2; return 1; } + value=$(aws secretsmanager get-secret-value --secret-id "$secret_name" --region us-east-1 --query SecretString --output text 2>/dev/null) \ + || { echo "error: failed to fetch secret '$secret_name' from AWS Secrets Manager (region us-east-1) - check that AWS credentials/profile are configured" >&2; return 1; } + [ -n "$value" ] && [ "$value" != None ] || { echo "error: secret '$secret_name' from AWS Secrets Manager returned an empty value" >&2; return 1; } + printf '%s' "$value" +} + +# fm_backend_cmux_env_args_for_harness: resolve 's secret specs (if +# any, per fm_backend_cmux_secret_specs_for_harness) and set +# FM_BACKEND_CMUX_ENV_ARGS to the "--env KEY=VALUE" pairs to pass to +# `new-workspace`. A fetched value only ever lands in this array - never +# printed, logged, or written to a file - mirroring how +# fm_backend_cmux_password/CMUX_SOCKET_PASSWORD above handles the socket +# password. Empty array (success) when the harness needs no injected secret. +fm_backend_cmux_env_args_for_harness() { # + FM_BACKEND_CMUX_ENV_ARGS=() + local harness=$1 spec env_var secret_name value + while IFS= read -r spec; do + [ -n "$spec" ] || continue + env_var=${spec%% *} + secret_name=${spec#* } + value=$(fm_backend_cmux_fetch_secret "$secret_name") || return 1 + FM_BACKEND_CMUX_ENV_ARGS+=(--env "$env_var=$value") + done < <(fm_backend_cmux_secret_specs_for_harness "$harness") + return 0 +} + # fm_backend_cmux_create_task: create the task's workspace (one surface), # refusing an existing live