Skip to content

A bypass session can still silence resume's config-change warning by deleting the out-of-tree baseline #571

Description

@pbean

Follow-up from the codex review of #570, which relocated the resume config-change baseline (trusted_config_digest) out of state.json into the run's out-of-tree state dir per the recorded 2026-08-08 decision on #498.

The residual

#570 closes the incidental silencing path: the pin is no longer a project file, so nothing a session does in the ordinary course of rewriting the tree can collaterally blank it. That is the case the advisory is documented to catch ("it catches the incidental change, not a deliberate one").

It does not close the deliberate path, and the review is right that this deserves stating plainly rather than as a footnote:

  • Every shipped profile launches with permission bypass — claude.toml --permission-mode bypassPermissions, codex.toml --dangerously-bypass-approvals-and-sandbox, gemini.toml --approval-mode=yolo, copilot.toml --allow-all-tools --allow-all-paths, antigravity.toml --dangerously-skip-permissions. This is the default operating mode, not an edge case; it is what an unattended loop is.
  • The session is handed BMAD_LOOP_EVENTS_DIR (engine.py), whose parent is the run's state dir. dirname() yields config-digest.
  • Deleting it makes the reader answer None → the legacy fallback reads state.json's field, which is "" for any run started after fix(resume): move the config-change baseline out of the agent-writable tree (#498) #570bool(pinned) false → no warning. Truncating it answers "" → same.

So a session that goes looking can still silence the warning; it just has to be deliberate about it rather than blanking a field it was already editing.

Why #570 did not fix it

Not an oversight — there is no sound fix at equal privilege, and the half-measures each cost something:

  • Treat absent/empty as tampering. Requires distinguishing "this run should have a baseline" from "genuinely legacy run". Any marker carrying that distinction has to live somewhere the same session cannot reach — and at equal privilege no such place exists. Put it in state.json and the session edits both; put it in the state dir and it is deleted with the file.
  • Warn whenever there is no baseline at all. Fires a false alarm on every genuinely legacy run's first resume, on a warning whose whole value is that it is rare. Training operators to ignore it is worse than the hole.
  • Hide the path. The run id and the state root are both derivable from the events dir the session must be given. A secret in the filename would have to be stored where resume (a fresh process) can find it — i.e. in-tree.

The direction that would actually work

Privilege separation on the state root, not a better hiding place: the control plane owned by a principal the driven session does not run as (separate user/service account, or ACLs that grant the session no write to the state dir while the orchestrator retains it), with the session given only what it must have — write access to events/ and nothing else in the state dir.

That is a real design change with real portability cost (POSIX ownership/ACLs vs Windows ACLs vs the WSL-UNC seam already tracked in #536/#552), and it would also harden the events channel, which today has the identical exposure — a session can delete events/ and stall its own run. Worth deciding as one piece rather than bolting a marker onto the digest.

Meanwhile

#570 states the limit explicitly in runs.config_digest_path_for's docstring and in the CHANGELOG entry, so the claim on record matches what the code delivers.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P4Parked - needs design, blocked upstream/PR, or speculativearea:configBMAD config resolution and the setup skillarea:engineOrchestrator engine and run lifecycleenhancementNew feature or requestneeds-designAwaiting a maintainer design decision before code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions