chore: promote staging to staging-promote/695e6fa1-24607103256 (2026-04-18 15:11 UTC)#2651
Conversation
…) (#2648) * fix(secrets): auto-generate master key via .env fallback on every startup (#1820) The "secrets store is not available" failure happened when a user reached `require_secrets_store` (web settings → save API key) but `SecretsConfig::resolve()` had returned `KeySource::None` because neither `SECRETS_MASTER_KEY` nor an OS keychain entry existed. This is the normal state on headless Linux, inside a container without secret-service, or after a partial onboarding that wrote `ONBOARD_COMPLETED=true` but didn't persist a key. The wizard's quick-mode `auto_setup_security()` already handled this chain correctly — env var → keychain read → keychain write → generate + persist to `~/.ironclaw/.env` as `SECRETS_MASTER_KEY=…` — but that code only ran during onboarding. If the user's gate slipped past `check_onboard_needed()`, nothing re-ran it. Move the chain into `SecretsConfig::resolve()` so it runs on every startup: - `resolve()` generates and persists a key via `upsert_bootstrap_vars_to()` (the same writer the wizard uses) when keychain is unavailable, and injects it into the process env overlay so the current run sees it. - `auto_setup_security()` collapses to a thin caller: `SecretsConfig::resolve()` → build `SecretsCrypto` → mirror source/hex into settings → print status. This removes the duplicate chain and keeps one persistence format (`.env`) and one `KeySource` (`Env`) for the keychain-unavailable path. Regression test `resolve_persists_generated_key_when_keychain_empty` drives `resolve_with_env_path()` with a tempfile and asserts the `.env` carries the generated key. Skips gracefully when the host keychain already holds a key (developer machines) so we don't wipe a real key. Supersedes #2312, which added a second persistence format (`~/.ironclaw/master.key`) + `KeySource::File` variant + `Keystore` trait alongside the existing `.env` path, with a known divergence-on-recovery gap between the two stores. Option B here reuses the single existing path instead. * ci: re-trigger regression check with skip-regression-check label
Code reviewFound 5 issues:
Secondary findings (lower priority):
🤖 Generated with Claude Code |
Auto-promotion from staging CI
Batch range:
a53eac5c2dec6b6cd5c08189086093fde64aa9cb..fcb295bad4eb0431fde626a4461446b2b571ef56Promotion branch:
staging-promote/fcb295ba-24607448875Base:
staging-promote/695e6fa1-24607103256Triggered by: Staging CI batch at 2026-04-18 15:11 UTC
Commits in this batch (118):
ironclaw profile listsubcommand (feat(cli): addironclaw profile listsubcommand #2288)Current commits in this promotion (1)
Current base:
staging-promote/695e6fa1-24607103256Current head:
staging-promote/fcb295ba-24607448875Current range:
origin/staging-promote/695e6fa1-24607103256..origin/staging-promote/fcb295ba-24607448875Auto-updated by staging promotion metadata workflow
Waiting for gates:
Auto-created by staging-ci workflow