Skip to content

fix(msb): kit environment[] never reached agent sessions — persist and replay it - #401

Open
basiliskus wants to merge 5 commits into
mainfrom
fix/issue-400-msb-kit-env
Open

fix(msb): kit environment[] never reached agent sessions — persist and replay it#401
basiliskus wants to merge 5 commits into
mainfrom
fix/issue-400-msb-kit-env

Conversation

@basiliskus

@basiliskus basiliskus commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #400

Problem

On the msb backend, a hybrid/v1 kit's environment block was applied only to the kit's own provisioning commands — it never reached the agent session or acq exec / acq shell. Any kit using environment for agent-runtime config (OPENCODE_CONFIG-style vars — the use case ADR-0011 added the vocabulary for) silently no-op'd on msb, while the same kit worked on sbx (which sets kit env at the sandbox level).

Reproduced live on msb 0.6.15: a minimal kit with environment: {PROBE_VAR: hello} provisioned cleanly, but acq exec <sandbox> -- printenv PROBE_VAR came back empty from every session path.

Fix

Mirrors the adapter's existing marker-file pattern (/var/lib/acq/agent, /var/lib/acq/ssh-auth-sock):

  • At kit apply: append the validated NAME=value entries to a root-owned guest marker, /var/lib/acq/kit-env. Entries are passed as argv to a fixed sh -c body — kit bytes are never interpolated into shell syntax (SI-10).
  • On every session path (attach, acq exec, acq shell): read the marker back and thread each entry as msb exec -e NAME=value. Names are re-validated against the kit_spec_env charset (tampered-marker defense) and the last value wins for a duplicate name, so a later kit overrides an earlier one — including mid-life acq kit apply re-applies.
  • Absent-marker crash guard (follow-up reported on the issue): with a /var/lib/acq marker file absent, the in-guest cat exits 1 inside the reader's command substitution and acq's set -euo pipefail killed the whole session verb (rc 1, no output). Verified live for the new kit-env marker (sandbox created before this feature) and the pre-existing ssh-auth-sock marker (no ssh-agent forwarding — its trailing | tr does not mask the failure; pipefail takes the failing stage's status). All four marker reads (kit-env, ssh-auth-sock, workspace, agent) are now failure-guarded.

Also amends ADR-0011's msb mapping (it documented the command-only scoping, which contradicted the block's own motivating use case) and deduplicates the env-collect loop in _acq_msb_run_commands into _acq_msb_collect_kit_env_into.

Testing

  • New test/bats/78-msb-kit-env.bats (6 tests, written failing-first): persist at apply, no marker without environment[], replay on exec, replay on attach/shell, absent-marker crash guard under set -euo pipefail, tampered-name drop + last-wins dedupe. The stubs now model an absent marker faithfully (exit 1 like real cat), so unguarded readers fail the suite.
  • Full offline bats suite green; shellcheck (--severity=warning, repo .shellcheckrc) and pinned markdownlint clean.
  • Live E2E on msb 0.6.15 (macOS/Apple Silicon): kit-declared var now reaches acq exec (previously empty); the pinned kits' entries append correctly and all arrive in sessions.

… sessions

On msb, a hybrid/v1 kit's environment[] entries were threaded only onto the
kit's own provisioning commands and never reached the agent session or
acq exec / acq shell, so agent-runtime config (OPENCODE_CONFIG-style vars —
the use case the vocabulary was added for) silently no-op'd. sbx has no such
gap because it sets kit env at the sandbox level.

Fix, mirroring the adapter's existing marker-file pattern
(/var/lib/acq/agent, /var/lib/acq/ssh-auth-sock):

- At kit apply, append the validated NAME=value entries to the root-owned
  guest marker /var/lib/acq/kit-env (entries passed as argv to a fixed sh -c
  body — kit bytes are never interpolated into shell syntax).
- On every session path (run/attach/shell), read the marker back and thread
  each entry as `msb exec -e NAME=value`, re-validating names and keeping the
  last value for a duplicate (later kits override earlier ones).

Also amends ADR-0011's msb mapping, which described the command-only scoping,
and deduplicates the env-collect loop in _acq_msb_run_commands into
_acq_msb_collect_kit_env_into.

Verified live on msb 0.6.15: a kit-declared var now reaches acq exec
(previously empty), with the pinned kits' entries appending correctly.

Fixes #400
…set -e

Follow-up reported on #400: with a marker
file absent, the in-guest `cat` exits 1 inside the reader's command
substitution and — since acq runs under `set -euo pipefail` — the whole
session verb dies with rc 1 and no output. Verified live on msb 0.6.15 for
both the new kit-env marker (any sandbox created before the feature) and the
pre-existing ssh-auth-sock marker (any sandbox without ssh-agent forwarding;
its trailing `| tr` does not mask the failure — pipefail takes the failing
stage's status). The workspace/agent marker reads in attach/shell share the
same shape, with documented fallbacks that were unreachable in practice.

Guard all four reads (`|| true` before the pipe / `|| _kvs=""`), and make the
test stubs faithful: an UNSET STUB_RECORDED_* now models an absent marker by
exiting 1 like real cat, so unguarded readers fail the suite. New regression
test runs exec/attach/shell under `set -euo pipefail` with every marker
absent.
…update note

Restores ADR-0011's original msb env-mapping wording (command-only scoping)
and records the new persist-and-replay behavior as a dated Update blockquote
instead — the repo's established ADR amendment convention — so the decision
record keeps what was originally decided.
… note

Comment-only: the set -e / absent-marker rationale was repeated at five
sites; keep the full explanation at _acq_msb_ssh_auth_sock_for and point the
other readers at it. No logic change.
@basiliskus
basiliskus marked this pull request as ready for review August 27, 2026 00:05
@basiliskus
basiliskus requested a review from a team as a code owner August 27, 2026 00:05

@mogul mogul left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Report

Reviewed PR #401 with an additional sub-agent review pass. The implementation is generally well covered by regression tests and the offline suite is passing. I found one medium-severity issue to resolve or explicitly accept before merge.

Finding

Medium: persisted kit environment can retain stale values after kit updates or re-applies

Files:

  • acq.backends/msb.sh:1477-1481
  • acq.backends/msb.sh:3299-3317
  • acq.backends/msb.sh:4336-4347

The new MSB kit environment replay appends current environment[] entries to /var/lib/acq/kit-env on every apply:

printf "%s\n" "$@" >> /var/lib/acq/kit-env

Replay later deduplicates duplicate names with “last value wins,” which handles changed values, but it does not remove variables that were present in an earlier kit version and later removed. Since acq_backend_ensure_kits_applied re-applies kits during heal/update flows, a removed runtime config value can remain active in future acq exec, acq run, and acq shell sessions indefinitely.

Impact: removed runtime config can keep influencing agent sessions after the kit no longer declares it. This matters for values like OPENCODE_CONFIG, host selectors, feature toggles, or other backend behavior controls.

Suggested fix: rebuild /var/lib/acq/kit-env from the current effective kit set during provision/heal, or store per-kit env markers and synthesize the effective runtime environment from currently applied kits. Please add a regression test that starts with an existing marker containing a value, reapplies/heals with a kit set that no longer declares it, and verifies sessions no longer receive that value.

Follow-Up / Optional Feature

environment[] values appear to be treated as single-line static strings today, and the current marker format is line-oriented. If multiline YAML scalar env values are intended to be supported, this PR should either implement an encoded marker format or explicitly reject multiline values.

If multiline env values are out of scope for this fix, I suggest tracking that as a follow-up issue so the current behavior is documented and intentional.

Nit

The PR title contains an em dash. CI accepts it, so this is not a blocker. If the project wants strict ASCII in titles, consider replacing it before merge.

Verification

Automated checks I ran:

  • PASS: git diff --check origin/main...HEAD
  • PASS: bash -n acq acq.backends/msb.sh scripts/test-acq-lib.sh
  • PASS: printf '%s\n' acq acq.backends/*.sh scripts/test-acq-lib.sh scripts/test-acq-bats test/bats/*.bats test/bats/helper.bash | xargs -r -n8 shellcheck --severity=warning
  • PASS: ./scripts/test-acq-bats test/bats/78-msb-kit-env.bats
  • PASS: ./scripts/test-acq-bats — 374 tests passing
  • PASS: npx --prefix .github/linters markdownlint-cli2 docs/adr/0011-msb-backend-and-neutral-kits.md
  • PASS: GitHub PR checks are green: Conventional Commit Title, Link Check, Markdown Lint, acq offline suite under bash 3.2, pre-commit
  • BLOCKED: gitleaks detect --no-git -v because gitleaks is not installed locally
  • PASS: diff-based secret-pattern fallback found only benign references to token, secret-store.sh, and ACQ_SECRET_STORE_DIR; no secret values observed

Sub-agent review independently confirmed the stale persisted environment finding.

@mogul

mogul commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Impact: removed runtime config can keep influencing agent sessions after the kit no longer declares it. This matters for values like OPENCODE_CONFIG, host selectors, feature toggles, or other backend behavior controls.

Why I'm asking for this to be fixed: I am dreading the experience of trying to support/debug inscrutable problems people report due to stale state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

msb: kit environment entries never reach the agent session — OPENCODE_CONFIG-style config silently doesn't load

2 participants