Skip to content
Merged
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
23 changes: 18 additions & 5 deletions .github/workflows/agent-files-detect.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false

Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
persist-credentials: false
Expand Down Expand Up @@ -86,9 +86,22 @@ jobs:
# $1 = diff range expression (e.g. "A..B" incremental or "A...B" net)
check_range() {
local range="$1"
local changed
changed=$(git diff --no-ext-diff --name-only "${range}")
DIRECT_MATCH=$(echo "${changed}" | grep -E '(^|/)(AGENTS\.md|CLAUDE\.md|GEMINI\.md)$|(^|/)skills/|^\.github/workflows/agent-files-(detect|enforce)\.yaml$' || true)
local changed all_paths
# Use --name-status to catch renames/deletes (exposes old path via R/D status lines).
# Format: "A\tpath", "M\tpath", "R100\told\tnew", "D\tpath"
changed=$(git diff --no-ext-diff --name-status "${range}")
# Extract all paths (both old and new for renames) to check against protected patterns.
all_paths=$(echo "${changed}" | awk '{
if ($1 ~ /^R/) {
# Rename: $2 = old path, $3 = new path
print $2
print $3
} else {
# Add/Modify/Delete: $2 = path
print $2
}
}')
DIRECT_MATCH=$(echo "${all_paths}" | grep -E '(^|/)(AGENTS\.md|CLAUDE\.md|GEMINI\.md)$|(^|/)skills/|^\.github/workflows/agent-files-(detect|enforce)\.yaml$' || true)
if [ -n "${DIRECT_MATCH}" ]; then
RANGE_RESULT="true"
elif git diff --no-ext-diff "${range}" | grep -qiE 'AGENTS\.md|CLAUDE\.md|GEMINI\.md|\.claude|\.cursor|\.vscode|\.agents|skills/'; then
Expand Down
42 changes: 40 additions & 2 deletions .github/workflows/agent-files-enforce.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@ jobs:
repo: context.repo.repo,
pull_number: parseInt(process.env.PR_NUMBER, 10),
});
const workflowPaths = [
'.github/workflows/agent-files-detect.yaml',
'.github/workflows/agent-files-enforce.yaml'
];
const changed = files.some(f =>
f.filename === '.github/workflows/agent-files-detect.yaml' ||
f.filename === '.github/workflows/agent-files-enforce.yaml'
workflowPaths.includes(f.filename) ||
(f.previous_filename && workflowPaths.includes(f.previous_filename))
);
core.setOutput('changed', changed.toString());

Expand Down Expand Up @@ -120,6 +124,32 @@ jobs:
console.log(`PR opener: ${u.login} (id ${u.id}, type ${u.type}); trusted_bot=${trusted}`);
core.setOutput('trusted_bot', trusted.toString());

# Verify workflow run head matches current PR head. If the PR was force-pushed or
# received new commits after the detect workflow started, the artifact is stale and
# must not be used to mutate labels or post status (would incorrectly approve/block
# a different commit than what the artifact describes).
- name: Verify artifact freshness
id: freshness-check
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
env:
PR_NUMBER: ${{ needs.resolve-pr.outputs.pr_number }}
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
with:
script: |
const prNumber = parseInt(process.env.PR_NUMBER, 10);
const workflowRunHeadSha = process.env.WORKFLOW_RUN_HEAD_SHA;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const currentHeadSha = pr.head.sha;
const fresh = workflowRunHeadSha === currentHeadSha;
if (!fresh) {
console.log(`Stale artifact detected: workflow run analyzed ${workflowRunHeadSha.substring(0,7)} but PR head is now ${currentHeadSha.substring(0,7)}. Skipping label mutation.`);
}
core.setOutput('fresh', fresh.toString());

- name: Download detection artifact
id: download
continue-on-error: true
Expand Down Expand Up @@ -149,6 +179,14 @@ jobs:
echo "${value}"
}

# Stale artifact: the workflow run analyzed a different commit than the PR's
# current head. Do not mutate labels (a newer detect run may already be queued).
if [ "${{ steps.freshness-check.outputs.fresh }}" != "true" ]; then
echo "Artifact is stale — skipping label mutation."
echo "label_action=neutral" >> "${GITHUB_OUTPUT}"
exit 0
fi

# Trusted bot (verified identity + all commits verified-signed): never label.
if [ "${{ steps.bot-check.outputs.trusted_bot }}" = "true" ]; then
echo "PR opened by a trusted bot — skipping label."
Expand Down
Loading