install: the rollback's sibling-scan decline claims a flag it may never have set

Open Beginner friendly
#723 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
84/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
git, python
Domain
cli, tooling

Research direction

Start in src/bmad_loop/install.py:1963-1968 and compare Block A with the hedged wording in _shield_undo_extension's tail. Check the shield bullet in docs/FEATURES.md and update the existing Block-A reason tests. Done means the sibling-scan report no longer overclaims flag provenance while rollback behavior remains unchanged.

Written by the indexing model from the issue text.

Description

area:engine bug P3

_shield_undo_extension's sibling-scan decline clause (src/bmad_loop/install.py:1963-1968)
reports the flag as a settled fact:

    if dependent is not None:
        return (
            "; extensions.worktreeConfig was deliberately LEFT enabled — "
            f"{dependent} exists, so another worktree's shield depends "
            "on the flag and unsetting it would stop git reading that file"
        )

"was deliberately LEFT enabled" asserts both that the flag is enabled and that this call
is what left it that way. Neither is guaranteed on the path that reaches it.

Why it can be false. One of the two call sites is the enable's own raise path
(install.py:2617-2627):

                try:
                    enabled = git_bytes(worktree, "config", "extensions.worktreeConfig", "true")
                    ...
                except GitError as e:
                    enable_fault = str(e)
                    rolled_back = _shield_undo_extension(worktree, git_dir, common_dir)

GitError there covers a spawn failure (GitSpawnError) and a timeout, so the write may
have produced nothing at all. If a sibling config.worktree then exists, the scan declines
and this clause claims a flag that may be:

  • absent — the enable never ran, and nothing set it; or
  • operator-authored — set outside bmad-loop, which the function's own docstring already
    says is possible ("an operator, an older bmad-loop, or git sparse-checkout can set the
    same flag outside it"). needs_enable is probe-derived and cannot tell "we enabled it"
    from "we both thought we did".

The asymmetry. The tail of the same function (install.py:1995-2004) already carries the
hedge discipline for exactly this reason, and says so in a comment:

    # Both clauses HEDGE whether this shield set the flag, and must: reached from the
    # enable's own raise, a spawn failure can kill the enable and this unset alike,
    # having written nothing at all. ...
    return (
        "; extensions.worktreeConfig could NOT be "
        f"rolled back ({detail}) — if this shield set the flag, the repository keeps "
        "a permanent format change that shields nothing"
    )

The comment says "Both clauses HEDGE" — Block B's wording does, Block A's never did.

Impact. Reporting only; the rollback's behavior is correct either way (declining to
unset a flag a sibling depends on is the right call, whoever set it). What is wrong is the
journaled/notified reason an operator reads: it can send them looking for a permanent
repo-format change this run did not make, or describe their own deliberate config line as
something bmad-loop left behind.

Suggested fix. Give Block A the same conditional voice as Block B — e.g. name the
observed dependency and hedge the provenance ("… is left as it stands", "if this shield set
the flag …") rather than asserting deliberately LEFT enabled. Docs that quote the
guarantee (docs/FEATURES.md shield bullet: "surviving in two cases the reason
distinguishes") should be checked for the same overclaim, and the existing Block-A reason
tests updated with it.

Provenance. Found during the #394 / #396 / #403 shield-transaction program while reading
_shield_undo_extension for #394 and #396. Deliberately left alone there — it is
pre-existing, wording-only, and outside all three issues' scope — and filed here instead.

Dominant language
Python
Stars
137
Forks
60
Avg merge
1d 3h
Merged PRs (30d)
44

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from bmad-code-org/bmad-loop

All issues in bmad-code-org/bmad-loop

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.