Skip to content

fix: run the app-ci path filter on pull requests only - #1

Merged
ttncode merged 1 commit into
mainfrom
fix/app-ci-filter-pull-requests-only
Sep 6, 2026
Merged

fix: run the app-ci path filter on pull requests only#1
ttncode merged 1 commit into
mainfrom
fix/app-ci-filter-pull-requests-only

Conversation

@ttncode

@ttncode ttncode commented Sep 5, 2026

Copy link
Copy Markdown
Owner

What this changes

app-ci.yml's changes job failed on every push to main after the very first one, in every project generated by the scaffold toolbox. ci and commitlint are gated on its output, so both were skipped — meaning no config root's ci-unit has ever run on main in any generated project. The merge gate existed only on pull requests.

Found independently by four end-to-end runs on four fresh private repositories, always the same three lines:

fatal: Not a valid object name 4271e418931ef22437ef5550aad8d6df69dd9148^{commit}
[command]/usr/bin/git fetch --depth=1 --no-tags origin 4271e418931ef22437ef5550aad8d6df69dd9148
fatal: could not read Username for 'https://github.com': No such device or address

The cause: the changes job checks out at the default fetch-depth: 1 with persist-credentials: false, then runs dorny/paths-filter. On a pull_request event paths-filter uses the GitHub API and is fine. On a push it must diff against github.event.before, which a depth-1 checkout does not have, so it tries to fetch it — and there is no credential left to authenticate with on a private repository. The first push to a branch works only because github.event.before is the all-zero SHA. This is the known interaction between persist-credentials: false and actions that shell out to git (reviewdog/reviewdog#2187, EndBug/add-and-commit#385).

The fix runs the filter on pull_request only, and falls back to the full inputs.roots on every other event. The filter is a pull-request time saver — most roots are untouched there. On main the opposite is wanted: a push to the default branch is exactly where every root should be verified, not a subset. That also removes the git dependency entirely, so persist-credentials: false stays and no fetch-depth change is needed.

How it was verified

  • mise run ci (this repo's own CI: zizmor .github/workflows/ then actionlint, then check) — EXIT=0, No findings to report.
  • The scaffold toolbox's tests/workflows.bats shape tests — 5/5 pass, including every checkout disables credential persistence.
  • Output shape proved by hand for both branches. ${{ steps.filter.outputs.changes || inputs.roots }}: a skipped step yields an empty string (falsy) so the input passes through, while "[]" is a non-empty string (truthy) and survives. Both produce a JSON array string that fromJSON accepts, and a pull request touching no root still yields exactly [], so the if: needs.changes.outputs.roots != '[]' guard still skips ci.
case roots ci runs
pull_request, both roots dirty ["apps/api","apps/web"] yes
pull_request, nothing dirty [] no
push (filter skipped) ["apps/api","apps/web"] yes

Note for the reviewer

Merging this is not the whole delivery. The v1 tag has to move for it to reach any generated project, and that reaches all of them at once (ADR-0005). That is a separate, deliberate step.

https://claude.ai/code/session_01J4HB8qJjdZwaAv42k6HMpv

dorny/paths-filter uses the GitHub API on a pull_request event, but on a
push it has to diff against github.event.before with git. That commit is
not in the changes job's default depth-1 checkout, so it fetches it — and
the checkout persists no credential (zizmor requires that), so on a
private repository the fetch dies with "could not read Username for
'https://github.com'". Every push to main after the first one therefore
failed the changes job, which skipped ci and commitlint: the merge gate
existed on pull requests only. The first push to a branch survived just
because github.event.before is the all-zero sha there.

Gate build-filters and the filter on github.event_name == 'pull_request'
and fall the job's roots output back to inputs.roots otherwise. The
filter is a pull-request time saver — most roots are untouched there —
while on the default branch every root should be verified anyway, so the
non-pull-request branch wants the full list regardless. Both branches
emit a JSON array string, so fromJSON and the ci job's != '[]' guard keep
working, no fetch-depth change is needed, and persist-credentials: false
stays.

Claude-Session: https://claude.ai/code/session_01J4HB8qJjdZwaAv42k6HMpv
@ttncode
ttncode merged commit 562e16c into main Sep 6, 2026
1 check passed
@ttncode
ttncode deleted the fix/app-ci-filter-pull-requests-only branch September 6, 2026 00:43
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.

2 participants