Skip to content

feat(plugin): check the plugin's folder before each copilot - #35

Merged
Bakhomious merged 1 commit into
mainfrom
step/plugin-check
Nov 3, 2026
Merged

Bakhomious merged 1 commit into
mainfrom
step/plugin-check

Conversation

@Bakhomious

Copy link
Copy Markdown
Contributor

Step plugin-check (docs/plan.md §0): baton checks the plugin's folder before each copilot starts, so that a role can't turn the guard off for the panes that start after it (risk 12). It follows the orchestrator's trust-gap note on #34: plugin.write reused an existing plugin-<hash> folder without looking at its files.

What it does

Decision 53:

  • plugin.Write uses a folder that has the plugin's name only if it is a real folder (not a symlink to one) and its files hash to the plugin's: paths in the folder and contents, as the binary's are hashed. A folder with a changed, extra or missing file, or with anything but folders and regular files (a symlink, say), and anything else under the name, stays as it is.
  • The plugin then goes under the next name, plugin-<hash>-1 up to -7; with all eight taken, the pane fails and says why. baton never replaces a folder (Go's os.Rename refuses to), so a starting copilot never finds the folder missing and goes on without hooks and with the default agent (risk 10). Panes that start at once converge on the same new name.
  • A role's pane writes the plugin again after its wait, just before it becomes copilot. A failure there leaves the work picked up, as a copilot that fails to start does (decision 38).
  • baton hook guard denies a role's create and edit in baton's cache folder, as in baton's folder, and fails when it can't find the user's cache folder.

The hash format changed (paths relative to the folder, regular files only), so the plugin's folder name changes once.

Checks

  • Tests: changed_test.go (8 kinds of change, each giving -1 and keeping the changed folder as the role left it, and many panes at once after a change), the keep test (same folder, same inode), recrafted hash pairs, refusal rows (a symlink in the tree, a name too long for the disk, a file at each of the 8 names); the guard's cache rows × create/edit and its failure without a cache folder; the pane's re-check after the wait and its failure. internal/plugin 100%, internal/cli 99.8%, total 99.3%.
  • Probes: 19 mutants, all caught.
  • Smoke with the built binary, a temp HOME and a stand-in copilot, no tmux: the first pane gets plugin-<hash>; after a change to hooks.json, -1, and -1 again; the changed folder stays as it was; the guard denies an edit of -1's hooks.json with the cache reason, says nothing for a repo file, and fails without HOME.

Known gaps

bash can still change the plugin's files after a pane's check, while its copilot starts or runs, or through a hard link (risk 2, for belt). Changed folders stay in the cache. A Finder .DS_Store in a folder costs one more folder.

Decision 53 is a lead's call, flagged for the end review.

A role runs as the user, so it could change the hooks in a plugin folder
in baton's cache and turn the guard off for the panes that start after
it (risk 12). Write now uses a folder only if its files hash to the
plugin's, and it is a real folder; else it writes the plugin under the
next name, up to seven more, and never replaces a folder that a copilot
may be loading. A role's pane checks again after its wait, and the guard
denies a role's create and edit in baton's cache (decision 53).
@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

@Bakhomious
Bakhomious merged commit 51364b1 into main Nov 3, 2026
3 checks passed
@Bakhomious
Bakhomious deleted the step/plugin-check branch November 3, 2026 22:35
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.

1 participant