Base-branch config-folder restore silently reverts changes that race the agent run (trusted triggers)
- Dominant language
- Go
- Stars
- 5.1k
- Forks
- 541
- Avg merge
- 5h 46m
- Merged PRs (30d)
- 760
Description
### Summary
The base-branch config-folder snapshot/restore ([#26380](https://github.com/github/gh-aw/pull/26380)) silently **reverts legitimate changes** to `.github` / `.claude` / etc. in the PR it produces, whenever the base branch advances during the agent run. The snapshot is taken at *activation* time but the PR is committed against a *later* base tip, so the force-restore writes stale config back over changes that landed in between — and these deletions are attributed to the agent PR.
This is noted as intentional ("protects against concurrent base branch mutations during execution"), but for **trusted, non-fork** triggers (`workflow_dispatch`, `command`, scheduled) it produces incorrect, confusing diffs rather than protecting anything, and it silently defeats the `create_pull_request` `allowed_files` scoping (the restore runs outside that filter).
### Repro (observed in production)
1. A `command`-triggered workflow with `safe-outputs.create_pull_request.allowed_files` scoped to a single file, and `protect_top_level_dot_folders: true`.
2. Activation job checks out base (`master`) at SHA `A` and snapshots `GH_AW_AGENT_FOLDERS` (incl. `.github`) into `/tmp/gh-aw/base`.
3. During the ~2-minute agent run, an unrelated human PR merges to `master` (SHA `B`), adding a step to `.github/workflows/publish.yml`.
4. `create_pull_request` commits the agent's change on top of `B`; `restore_base_github_folders.sh` overwrites `.github` with the SHA-`A` snapshot.
5. Resulting PR diff: the intended one-line change **plus** a spurious deletion of the step added in `B` — even though the agent never touched `.github` and it isn't in `allowed_files`.
Confirmed at the git level: the workflow blob in the agent's commit is byte-identical to `^` (pre-change base), while the commit's parent already contains the change.
### Impact
- Silent reverts of workflow/config changes that raced the run — a correctness and, for `.github`/`.mcp.json`, a supply-chain-adjacent concern.
- `allowed_files` gives false confidence: it does not bound what the PR actually changes.
- Noisy PRs that reviewers must manually clean up (or worse, merge).
### Suggested fixes (either)
1. **Re-snapshot against the base tip at PR-creation time** (re-fetch base right before `restore_base_github_folders.sh`) so the restore reflects the actual PR base, not the activation-time base.
2. **Scope the force-restore to untrusted contexts only** (fork PRs / `pull_request_target` / `issue_comment`), where the threat model applies. Trusted `workflow_dispatch`/`command`/schedule runs don't need it.
3. At minimum, extend the `allowed_files` guard to the restore step so config-folder writes outside the declared scope are dropped rather than committed.
### Environment
- setup action `v0.77.5`, firewall/agent images `0.25.58`.
- Scripts `actions/setup/sh/{save,restore}_base_github_folders.sh` unchanged on `main` at time of filing.
Contributor guide
Research direction
Start with actions/setup/sh/save_base_github_folders.sh and restore_base_github_folders.sh, then trace the create_pull_request path that invokes the restore. Reproduce the race with a base change during an agent run and inspect the resulting diff. Done means trusted runs no longer revert intervening base changes or bypass allowed_files, while the intended protection remains covered by tests or a documented verification.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github-actions, shell
- Domain
- ci-cd, devops, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100