Skip to content

Bootstrap kept-tree rollback can leave no tree, and a hand-run report mislabels its commit #1770

Closed

Description

@ptr727

Two narrow gaps in the kept-tree handling the #1756 change added to host-setup/bootstrap.sh, bootstrap.ps1, and scripts/skills_install.py.

The rollback can leave no tree

The swap moves the old tree aside to .old and then renames .new into place. If that second rename fails, the swap renames .old back. If the rollback rename fails as well, or an interrupt lands between the two renames, the exit cleanup deletes both .new and .old. Nothing is then left at the tree's name, and the plugin loads nothing. In bash, a failing rollback mv trips set -e. In PowerShell, a throwing rollback Move-Item escapes the catch into finally.

  • Fix: where the tree path is absent, cleanup renames an owned .old back rather than deleting it.

A hand-run report mislabels the commit's source

intended_commit labels an archive commit's ref SKILLS_SOURCE_COMMIT. Since #1756, source_ref also reads the commit from the tree's .bootstrap-commit marker. A --report run by hand from a kept tree, with that variable unset, therefore names a variable nobody set.

  • Fix: label the ref by whichever source supplied the commit.

Found by the local strict review on the #1756 change, and deferred at the maintainer's stop point.

Activity

  1. ptr727 commented on Sep 24, 2026

    @ptr727
    OwnerAuthor

    A third gap of the same kind, found by the local strict review on #1773.

    bootstrap.sh removes an owned tree with rm -rf, which can delete the .bootstrap-owned marker before the rest of the tree. If that removal stops part way, for example on a signal or on one entry that fails, the leftover directory carries no marker. The loader then no longer counts it as its own. Every later swap dies with "exists and this loader did not create it ... Choose another --dir", and that remedy is wrong: the fix is to delete the leftover.

  2. ptr727 commented on Sep 24, 2026

    @ptr727
    OwnerAuthor

    Two more of the same kind, both predating #1756, found by the local strict review on #1773:

    • --report --intended REV run from a bootstrap-extracted tree still asks git in that tree. When a revision is named, intended_commit skips the archive short-circuit. If the tree sits under a home directory kept in git, the enclosing repository resolves REV, and the report calls the copy stale against a commit from an unrelated repository. The loaders never pass --intended, so only a run by hand reaches this.
      • Fix: in a bootstrap tree, resolve nothing for a named revision, or refuse --intended there.
    • The loaders' report message is wrong for a commitless tree. With the ref unresolved, --report exits 1 with "cannot be judged", and the loader then prints "missing or stale, and --host or --skills lands it". scripts/README.md says a re-install does not fix that state.
      • Fix: word the loader message by the report's reason rather than by the exit code alone.
  3. ptr727 commented on Sep 24, 2026

    @ptr727
    OwnerAuthor

    Two more from the local strict review on #1773, both on bootstrap.ps1's Invoke-Cleanup in the double-failure case, where a swap's rename fails and so does its rollback.

    • The staging tree is removed before the old tree is restored, with a plain Remove-Item -Recurse. The lock that makes the swap's rename fail, such as an antivirus scan of the freshly extracted tree, also makes this removal throw part way. That skips the restore and its "move it by hand" warning. The removal can also delete .bootstrap-owned first, leaving a staging tree the next run refuses as not its own.
      • Fix: restore first. Then remove the staging tree marker-last and best-effort, the way Remove-Retired removes the old tree. This is the same fix as the bash marker-order point above, applied to every removal of an owned tree.
    • The "move it by hand" warning also fires for a transient tree, which nothing reads.
      • Fix: gate that warning on a kept tree.
  4. ptr727 commented on Sep 24, 2026

    @ptr727
    OwnerAuthor

    One more, from Copilot on #1773: remove_owned and is_ours test [[ -e ]], so a dangling symlink at a managed path such as skills-tree.new reads as absent. mkdir -p then fails with a generic "File exists". Nothing is removed. Fix: treat -L as existing in the ownership check, and refuse with the loader's own message.

  5. ptr727 commented on Sep 24, 2026

    @ptr727
    OwnerAuthor

    One more, from CodeRabbit on #1774 (bootstrap.ps1 Invoke-SwapIn): when skills-tree is absent and only a locked skills-tree.old remains, the swap first tries to remove .old. That fails on the lock, so the new staging tree is never promoted, and the registered name stays empty. Fix: skip the pre-removal of .old when the tree name is empty. Keep the refusal when both exist, since .old has to be free before the live tree moves aside.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions