Skip to content

Restrict workflow pushes by bot identity, not by our App's name - #118

Merged
charlesgreen merged 3 commits into
mainfrom
fix-adopter-preflight
Aug 2, 2026
Merged

charlesgreen merged 3 commits into
mainfrom
fix-adopter-preflight

Conversation

@charlesgreen

Copy link
Copy Markdown
Contributor

Closes #111.

The fix

simplycubedAppLogin is gone. WorkflowRestrictedPush is now isBotLogin(forge.Self) — any *[bot] login, since the Actions runtime always authenticates as an App and that App holds no workflows permission by design.

An empty login stays unrestricted. That is a local run under a human's own credential, and a human can push workflow files. main.go already documented empty-means-human; nothing pinned it until now.

workflowPermissionReason took our bot's name in prose, so an adopter's correct escalation pointed at an account they had never installed. It now takes the authenticated login, falling back to "a GitHub App" when there is none. That meant threading SelfLogin through app.Deps and loop.Config, and giving workflowPushReason a receiver.

Tests

Two cases, neither previously covered:

  • an adopter's bot (acme-code[bot]) is restricted — the case that was broken, and the one that cannot reproduce in this repository
  • an empty login is not restricted — the human case, which a suffix check gets right by accident and a refactor could silently break

Injected with SIMPLYCUBED_SELF_LOGIN through prepare(), so it is hermetic and needs no network.

Why a human opened this

The agent was given sc:go on #111 and produced essentially this change, then blocked with "the change touches .github/workflows/". It does not. The identical change here modifies four Go files, git status --porcelain -- .github/workflows returns empty, and make check passes.

That escalation was a false positive inside the agent's worktree, and the same run reported go build failing there with error obtaining VCS status: exit status 128. Filed separately — it is an environment bug, not this one.

App names are globally unique, so every adopter's App resolves to a
different bot login and the hardcoded comparison was false for all of
them. The preflight only ran for the one installation that needed it
least, and everyone else discovered the refusal at push time, after a
full loop and its model spend.

Any *[bot] login is restricted: the Actions runtime always authenticates
as an App and that App deliberately holds no workflows permission. An
empty login stays unrestricted, because that is a local run under a
human's own credential and a human can push workflow files.

The escalation message named our bot in prose too, so an adopter's
correct escalation pointed at an account they never installed. It now
names the authenticated identity, or says 'a GitHub App' when there
isn't one.

Closes #111
prepare() requires the Azure variables, and the two new subtests relied on
them being present in the shell. They passed locally and failed on a clean
runner, which is the failure mode the tests exist to prevent.

Matches the pattern the surrounding prepare tests already use.
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.57143% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/loop/loop.go 70.00% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Codecov flagged internal/loop at 70% patch coverage. The uncovered branch
was the new one: naming the authenticated identity. The existing tests
only ever ran with an empty login, so they exercised the generic wording
and never the adopter case.

Adds coverage for both escalation paths naming a real bot, the generic
form when no login is known, and the two push-refusal branches that must
stay silent.

Also covers the preflight deciding NOT to block. That path had no test at
all, and it is the exact behaviour that misfired on the run for this
issue (#119): a change touching no workflow file must reach a pull
request, and a failure to decide must not be reported as a permission
problem.

internal/loop goes from 86.0% to 89.3%.
@charlesgreen
charlesgreen merged commit 1f6ac69 into main Aug 2, 2026
2 checks passed
@charlesgreen
charlesgreen deleted the fix-adopter-preflight branch August 2, 2026 02:41
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.

The workflow-file preflight never fires for an adopter, only for us

1 participant