Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 93 additions & 38 deletions .github/workflows/no-mistakes-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:

permissions:
contents: read
# Read-only, and only used to re-read this PR's live body when the event
# payload carries a body no-mistakes has already replaced.
pull-requests: read

# GitHub concurrency groups retain at most one pending run, replacing older
# pending runs even when cancel-in-progress is false. Give body-bearing events
Expand All @@ -31,23 +34,35 @@ jobs:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
marker='Updates from [git push no-mistakes](https://github.com/kunchenguid/no-mistakes)'
if printf '%s' "${PR_BODY:-}" | grep -qF -- "$marker"; then
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
prefix='<!-- no-mistakes-pipeline-attestation:v1 '
suffix=' -->'

# Classify one body snapshot into `verdict` (ok, no-signature,
# no-attestation, incomplete) plus, for incomplete, `incomplete`.
verdict=''
incomplete=''
classify_body() {
snapshot=$1
verdict=''
incomplete=''
if ! printf '%s' "$snapshot" | grep -qF -- "$marker"; then
verdict=no-signature
return 0
fi
if ! command -v jq >/dev/null 2>&1; then
echo "::error::This check requires jq to parse no-mistakes pipeline step attestation, but jq was not found on the runner." >&2
exit 1
fi
prefix='<!-- no-mistakes-pipeline-attestation:v1 '
suffix=' -->'
body="${PR_BODY:-}"
json=''
parse_ok=0
case "$body" in
case "$snapshot" in
*"$prefix"*)
rest="${body#*"$prefix"}"
rest="${snapshot#*"$prefix"}"
case "$rest" in
*"$suffix"*)
json="${rest%%"$suffix"*}"
Expand All @@ -59,6 +74,57 @@ jobs:
;;
esac
if [ "$parse_ok" -ne 1 ]; then
verdict=no-attestation
return 0
fi
for required in review test document; do
status=$(printf '%s' "$json" | jq -r --arg step "$required" \
'([(.steps | arrays | .[]) | select(.step == $step) | .status] | first // empty | select(. != "")) // "missing"')
if [ "$status" != "completed" ]; then
if [ -n "$incomplete" ]; then
incomplete="${incomplete}, "
fi
incomplete="${incomplete}${required}=${status}"
fi
done
if [ -n "$incomplete" ]; then
verdict=incomplete
return 0
fi
verdict=ok
}

# no-mistakes pushes the branch and opens the PR before it writes the
# pipeline body, so an opened/synchronize payload can carry a body
# that was already replaced by the time this job starts. Re-read the
# live body before failing, so the gate reports the PR's real state
# instead of a race it cannot influence. Every event is still
# evaluated on its own run against the body that is live for it.
body="${PR_BODY:-}"
attempt=1
attempts=6
while :; do
classify_body "$body"
if [ "$verdict" = ok ] || [ "$attempt" -ge "$attempts" ]; then
break
fi
echo "PR #${PR_NUMBER} body is not compliant yet (${verdict}); re-reading the live body (attempt ${attempt} of $((attempts - 1)))."
sleep 10
attempt=$((attempt + 1))
if live=$(gh api "repos/${PR_REPO}/pulls/${PR_NUMBER}" --jq '.body // ""' 2>/dev/null); then
body="$live"
else
echo "Could not re-read the PR body from the API; keeping the event payload body."
fi
done

case "$verdict" in
ok)
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
echo "Pipeline step attestation is valid: review, test, and document are completed."
exit 0
;;
no-attestation)
{
echo "::error::This repository requires no-mistakes >= 1.46.0; structured pipeline step attestation is missing or unparseable."
echo
Expand All @@ -77,19 +143,8 @@ jobs:
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi
incomplete=''
for required in review test document; do
status=$(printf '%s' "$json" | jq -r --arg step "$required" \
'([(.steps | arrays | .[]) | select(.step == $step) | .status] | first // empty | select(. != "")) // "missing"')
if [ "$status" != "completed" ]; then
if [ -n "$incomplete" ]; then
incomplete="${incomplete}, "
fi
incomplete="${incomplete}${required}=${status}"
fi
done
if [ -n "$incomplete" ]; then
;;
incomplete)
{
echo "::error::Required no-mistakes pipeline steps are not completed: ${incomplete}."
echo
Expand All @@ -103,21 +158,21 @@ jobs:
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
fi
echo "Pipeline step attestation is valid: review, test, and document are completed."
exit 0
fi
{
echo "::error::This PR was not raised through no-mistakes."
echo
echo "Contributions to this repository must be submitted via 'git push no-mistakes'."
echo "That pipeline runs the required review/test/lint/CI steps and writes a"
echo "deterministic '## Pipeline' section into the PR body containing:"
echo
echo " $marker"
echo
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
;;
*)
{
echo "::error::This PR was not raised through no-mistakes."
echo
echo "Contributions to this repository must be submitted via 'git push no-mistakes'."
echo "That pipeline runs the required review/test/lint/CI steps and writes a"
echo "deterministic '## Pipeline' section into the PR body containing:"
echo
echo " $marker"
echo
echo "See CONTRIBUTING.md for setup and the full workflow."
echo
echo "PR author: ${PR_AUTHOR}"
} >&2
exit 1
;;
esac
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Pushing through it runs an AI-driven review/test/lint pipeline in an isolated wo

A GitHub Actions check (`Require no-mistakes`) runs on PRs targeting `main` and fails if the body is missing the deterministic signature that no-mistakes writes.
It evaluates every PR opening and body edit independently, so a later edit cannot replace an earlier pending compliance check.
Because no-mistakes pushes the branch and opens the PR before it writes the pipeline body, a run whose event payload lacks that signature re-reads the PR's live body for up to a minute before failing, so the gate never reports a race it cannot influence.
GitHub Actions and Dependabot are exempt so their automation keeps working, but regular contributor PRs without the signature will not be reviewed or merged.

## Workflow
Expand Down
16 changes: 16 additions & 0 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2259,6 +2259,22 @@ elif [ "$KIND" != secondmate ] && [ "$BACKEND" != orca ]; then
fi

validate_spawn_worktree "treehouse get" "$T"

if [ -x "$SCRIPT_DIR/fm-treehouse-pool-sweep.sh" ]; then
sweep_rc=0
FM_CONFIG_OVERRIDE="$CONFIG" \
"$SCRIPT_DIR/fm-treehouse-pool-sweep.sh" "$WT" || sweep_rc=$?
if [ "$sweep_rc" -ne 0 ]; then
# The refusal deliberately keeps the acquired slot: the sweep refuses
# precisely when the worktree may hold work nothing else references, and
# returning it here would discard exactly that. The slot stays held, and
# window $T stays open, until an operator has looked at the state; say so
# rather than leaving the hold to look like a leak.
echo "error: worktree pool sweep refused worktree $WT (exit $sweep_rc); inspect unsafe state or disable sweep in config/worktree-pool-sweep" >&2
echo "error: the pool slot for $WT stays held and window $T stays open so its unsafe state is preserved for inspection; return it by hand once its work is safe" >&2
exit 1
fi
fi
fi
if [ "$RELAUNCH" -eq 0 ] && [ "$KIND" != secondmate ]; then
freshen_spawn_worktree_base "$WT" || exit 1
Expand Down
Loading
Loading