Skip to content

fix(ci): run commit lint directly so the policy gate executes - #586

Merged
ss-o merged 3 commits into
mainfrom
bug-575
Sep 2, 2026
Merged

fix(ci): run commit lint directly so the policy gate executes#586
ss-o merged 3 commits into
mainfrom
bug-575

Conversation

@ss-o

@ss-o ss-o commented Sep 2, 2026

Copy link
Copy Markdown
Member

Root cause

lint-commits.yml declared workflow-level permissions: {} and then called
commit-lint.yml, whose commit-message job requests contents: read. A
called workflow's jobs cannot hold more permission than the caller job, so
GitHub rejected the run at creation time. Every run since the workflow landed
in #517 recorded startup_failure:

gh api repos/z-shell/.github/actions/workflows/lint-commits.yml/runs \
  --jq '[.workflow_runs[].conclusion] | unique'
["startup_failure"]

gh api repos/z-shell/.github/check-suites/90220512465 \
  --jq '{conclusion, latest_check_runs_count}'
{"conclusion":"startup_failure","latest_check_runs_count":0}

No check runs and a 404 on the logs endpoint, which is why the failure stayed
invisible. actionlint passes the pair cleanly, so nothing local caught it.

The other two causes suggested in #575 are not involved. The regex defaults are
single-quoted YAML scalars and parse literally, and a local ./ reusable
reference resolves normally for a same-repository pull_request.

Fix

commit-lint.yml now carries the pull_request trigger and concurrency group
itself and keeps workflow_call for other repositories. lint-commits.yml is
deleted. That is the shape already proven in this repository by
labels-sync-test.yml, repo-settings-audit-test.yml, and
labeler-config-audit-test.yml, and in z-shell/zi:.github/workflows/commit-lint.yml,
where the same three jobs report success.

A second defect would have surfaced the moment the workflow started running.
workflow_call input defaults are not applied to a pull_request run, where
the inputs context is empty, so DISALLOWED_TRAILER_PATTERN and
BRANCH_PATTERN would both have expanded to the empty string. An empty
grep -E pattern matches every line, which means every commit would have been
flagged as carrying a bot trailer and every branch name would have passed
silently. The fallbacks now live in the job steps as : "${VAR:=default}", and
the on.workflow_call.inputs.*.default values are gone so there is one source
of truth per pattern.

The unused pull-requests: read is dropped from the commit-message job. It
reads git history only, and a lower ceiling is one less permission a future
caller has to grant.

PATTERNS.md records the caller-permission ceiling and the input-default trap.

Verification

  • actionlint and trunk check are clean on both files.
  • The fallback patterns were exercised directly: a [bot] trailer is blocked,
    a human Co-authored-by is allowed, a plain subject is unaffected, and
    bug-575 matches the branch pattern while code/foo and next do not.
  • Running the new job logic over this branch's own commits gives
    errors=0 checked=2.
  • This pull request is the live verification fix(ci): Lint Commits fails at startup so commit policy is unenforced #575 asked for. Validate Commits, Validate PR Title, and Validate Branch Name should all appear
    and pass here for the first time.

Follow-up for a maintainer

  1. The main ruleset still requires no status checks. Once the three contexts
    are observed on this pull request they can be added to
    required_status_checks. runbooks/branch-protection.md notes that GitHub
    only accepts contexts it has already seen. Because the workflow is no longer
    nested, the contexts are Validate Commits, Validate PR Title, and
    Validate Branch Name, without a commit-lint / prefix.
  2. This workflow has never executed, so the next pull request opened from a
    code/* or codex/* branch will fail Validate Branch Name against
    ^(feature|bug|hotfix)-[1-9][0-9]*$ from decisions/0019. That is the policy
    working rather than a regression from this change. Widening the pattern is a
    separate decision.

Closes #575

ss-o added 2 commits September 2, 2026 13:28
The Lint Commits caller declared workflow-level `permissions: {}` and then
called `commit-lint.yml`, whose `commit-message` job requests `contents: read`.
A called workflow cannot hold more permission than its caller job, so every run
was rejected at run creation: 20 of 20 recent runs recorded `startup_failure`
with zero check runs and no logs, and the commit-message, PR-title, and
branch-name policy was never evaluated.

Collapse the caller into `commit-lint.yml`, which now triggers on
`pull_request` into `main` and keeps `workflow_call` for other repositories.
That is the shape used by the other reusable suites here and by z-shell/zi,
where the same three jobs pass.

`workflow_call` input defaults do not apply to a `pull_request` run, so the
patterns now fall back in the job step. Without that, the empty trailer pattern
would match every commit and the empty branch pattern would pass every branch.

Drop the unused `pull-requests: read` from the commit-message job; it reads git
only, and a lower ceiling is one less permission a future caller must grant.

Closes #575
Learning capture for #575. A caller workflow with `permissions: {}` caps every
nested job at no permissions, so the run is rejected at creation with no logs
and actionlint stays silent. Record the failure shape, the two-repository
precedent for self-triggering shared workflows, and the workflow_call input
default trap that comes with them.
@ss-o
ss-o requested a review from a team as a code owner September 2, 2026 12:33
The PATTERNS.md addition in this branch feeds
test_repair_2_consumer_parser_outputs_match_frozen_golden, which hashes the
visible markdown of seven consumer surfaces. Refresh the digest the same way
#582 did for its skill edit. Verified the golden passes unchanged against
PATTERNS.md from main, so the branch content is the only input that moved.
@ss-o
ss-o merged commit 3058165 into main Sep 2, 2026
10 checks passed
@ss-o
ss-o deleted the bug-575 branch September 2, 2026 12:41
ss-o added a commit that referenced this pull request Sep 2, 2026
The digest covers seven consumer surfaces, PATTERNS.md and .github/README.md
among them, so editing any of them fails Validate Agent Instructions with
nothing but two bare SHA-256 strings. Nothing in AGENTS.md, runbooks,
decisions, or the scoped instructions records that, so #582 and #586 both
discovered the regeneration step by failing CI.

Attach the seven paths and the procedure to the assertion's msg=, where the
person who needs it is already looking. One argument covers every surface.

Closes #588
ss-o added a commit that referenced this pull request Sep 2, 2026
Validate Branch Name became a blocking required check here after #586, and it
rejected two shapes that are not a naming discipline problem.

copilot/ and codex/ join dependabot/ and renovate/ in the always-allowed
prefixes, because a coding agent picks those names rather than the
pull-request author. The pattern becomes
^(feature|bug|hotfix)-[1-9][0-9]*(-[a-z0-9]+)*$, so a descriptive slug may
follow the issue id, which the old trailing anchor rejected on branches such as
feature-478-repo-settings-audit.

The issue id stays mandatory for author-chosen branches, so decisions/0019 is
unchanged in substance. Shapes carrying no issue id at all, code/, fix/, feat/,
docs/, ci/, chore/, remain rejected; admitting those would reverse the
issue-linked naming the ADR decided and belongs in an ADR amendment.

Verified with a table over 18 branch names covering both new allowances, the
existing bot prefixes, next, and the shapes that must keep failing.

Closes #590
ss-o added a commit that referenced this pull request Sep 2, 2026
Three defects have shipped in commit-lint.yml. #575 could not start at all and
took 53 runs to notice. The empty-pattern trap found during #586 would have
flagged every commit and passed every branch. #587 failed open on a large
commit message. Two of the three were silent, and nothing automated caught any
of them.

Add a suite that extracts every pattern from the workflow rather than
restating it, so the test cannot drift into a second source of truth, and
assert the constructs as well as the patterns: no grep -q on the trailer
pipeline, an in-step fallback for each pattern, and no workflow_call input
default that a pull_request run would ignore.

Verified by mutation. Six deliberate regressions were introduced one at a time
and every one failed the suite: reintroducing grep -q, removing a fallback,
loosening the branch pattern to accept feature-0, dropping the copilot and
codex prefixes, widening the trailer pattern to ban human co-authors, and
raising the subject cap past 72.

Closes #592
ss-o added a commit that referenced this pull request Sep 2, 2026
Section 4 required a default on every workflow_call input. Two shipped
workflows contradict that for two independently correct reasons.

zsh-lint.yml declares both its inputs required, and a default on a required
input is unreachable because the caller always supplies the value.

commit-lint.yml declares workflow_call alongside pull_request. GitHub scopes
the inputs context to a reusable or manually triggered workflow, so on a
pull_request run it is empty and the declared defaults never apply. #586
removed them for that reason; #597 now asserts no such default exists.

Split the bullet into three clauses so the rule matches both shapes. This is a
mandatory surface, so as written an agent would have edited both workflows back
into defects. Instruction impact review is recorded in the pull request.

Closes #598
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.

fix(ci): Lint Commits fails at startup so commit policy is unenforced

1 participant