zudo-text

検索したい単語を入力

いつでも検索バーを開ける

CI-Skip Markers and Merge Hygiene

CI-skip markers are useful in the PR-first workflow, but a marker on the wrong commit can suppress every workflow triggered by a push — including production deployments. This page records the convention for keeping that failure mode visible and contained.

The invariant

No commit that becomes the push head on main may contain a CI-skip instruction.

The rule is about the push head, not every commit that lands on main. GitHub evaluates the message of the commit at the head of the push when it decides whether to skip workflows. A regular merge can therefore retain a marked start commit as an ancestor in main's history without suppressing the workflows for the merge commit itself. That distinction is why the guard never scans branch commit subjects: the PR-first workflow's empty start commit is expected to carry a marker. Under the repository's PR_TITLE + BLANK settings, the PR title is the only PR text that can become the squash commit message, so the pull-request guard scans that title and checks the settings for drift before merge.

The guard recognizes all six GitHub skip instructions, case-insensitively:

  • [skip ci], [ci skip], [no ci], [skip actions], and [actions skip]

  • The skip-checks: true or skip-checks:true trailer

Why the marker exists

The PR-first workflow opens a draft PR with an empty-diff start commit. Its [skip ci] subject suppresses the pull_request: opened run that would have no changes to test. The marker is intentional on that branch commit and remains harmless as long as it does not become the message of the commit pushed to main.

Before the repository setting was corrected, squash merging was the propagation channel. squash_merge_commit_message: COMMIT_MESSAGES tells GitHub to concatenate the branch commit subjects into the squash commit body. GitHub's skip detection reads the whole commit message, including the body, so the start commit's marker reached main and suppressed every push-triggered workflow. The safe repository settings are PR_TITLE for squash_merge_commit_title and BLANK for squash_merge_commit_message, which make the squash commit message exactly the PR title.

Evidence from the incident

The failure was silent: the marked squash commits produced no workflow runs at all.

CommitMerge styleMarkerWorkflow runs
a81aa72a (PR #5360, touched tauri-app/**)squashyeszero
49e613a0 (PR #5011)squashyeszero
Current main HEAD (control)regular mergenoCI + Preview Deploy + Deploy Web Editor, all green

deploy-web-editor.yml listens for push: main changes under tauri-app/**, while preview-deploy.yml has no path filter on its push trigger. The first marked squash therefore also skipped a production deploy, even though the merge itself looked successful.

Merge-mode inventory

ModeMarker reaches the push head?Notes
Regular merge (house default)NoThe merge commit message never contains branch commit subjects.
Squash, after this fixNoThe message is exactly the PR title.
Squash, before this fixYesThis is the observed bug.
Rebase mergeNoGitHub drops commits that were empty to begin with.
gh pr merge --squash --body "…"Yes — residualAn explicit message overrides the repository settings; this cannot be closed without a required check.
Merge queueN/AUnavailable on this plan. If it becomes available, the guard must also subscribe to merge_group.

Verify that a merge ran its workflows

Workflow creation can lag behind the merge. Poll the main branch and match the merged commit's headSha on the client side:

merge_sha="<merge-sha>"
gh run list --branch main \
  --json headSha,workflowName,status,conclusion \
  | jq --arg sha "$merge_sha" '[.[] | select(.headSha == $sha)]'

Repeat the query with a bounded retry until the expected workflows appear and reach a terminal status. Check workflowName, status, and conclusion, not just whether one run exists. For a merge touching .github/** and scripts/**, the expected set includes at least CI and Preview Deploy; changes under tauri-app/** also make Deploy Web Editor relevant.

Do not use gh run list --commit <sha> for this check. It has been observed to omit queued runs, and “not created yet” is not the same as “suppressed”.

Residual risks

Two paths cannot be closed by this repository alone:

  1. gh pr merge --squash --body "…" supplies an explicit squash message and overrides the repository settings. That later CLI override is invisible to the pull-request title guard, and this repository has no required policy capable of blocking it.

  2. This repository is private on a free organization plan. Branch protection and required status checks are unavailable — the protection and ruleset API returns 403 — so the guard reports but cannot block. The repository's PR_TITLE + BLANK squash settings are the enforcing mechanism.