Skip to content
Open
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
28 changes: 28 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Claude Development Notes

This file contains guidance for Claude Code when working in this repository.
It is excluded from distributions via `.gitattributes export-ignore`.

## CI Monitoring After Every Push

**REQUIRED**: After every `git push`, immediately start a background task to
monitor the CI run for that push. If you pushed to both pgxntool and
pgxntool-test, start a background task for each repo — do not monitor them
sequentially.

Use `gh run watch` or poll with `gh run list` / `gh pr checks` in the
background task. Report failures to the user as soon as they are detected;
do not wait for all jobs to finish before reporting.

## Multiple Concurrent Sessions

It is common to have multiple Claude Code sessions open simultaneously across
pgxntool and pgxntool-test. To avoid cross-session interference:

**If you are asked to do something on an existing PR that you did not open or
are not already working on in this session, immediately ask for confirmation
before proceeding.** For example: "I see PR #32 exists. Were you asking me to
work on that, or did you mean to send this to a different session?"

This applies to: editing PR branches, pushing to them, closing/reopening them,
adding commits, modifying PR descriptions, or any other PR-level action.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.gitattributes export-ignore
.claude/ export-ignore
.github/ export-ignore
*.md export-ignore
.DS_Store export-ignore
*.asc export-ignore
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# .github/workflows — CI Architecture

## Workflow files

- **`ci.yml`** — main CI for pgxntool pull requests. Runs `check-test-pr` (verifies
the paired pgxntool-test PR's CI passed), then optionally runs `test` (only for the
commit-with-no-tests path — see below).
- **`protect-label.yml`** — enforces that only maintainers with write access can apply
or remove the `commit-with-no-tests` label.

## Normal CI flow (paired test PR exists)

When a pgxntool PR has a corresponding open PR in pgxntool-test with the same branch
name, the `check-test-pr` job polls (up to 20 minutes) for that test PR's CI to
complete and pass. If it passes, pgxntool CI passes — **no tests run here**. Tests run
exactly once, in pgxntool-test's own CI.

## commit-with-no-tests path

When a maintainer applies the `commit-with-no-tests` label (and no paired test PR
exists), the `test` job runs tests directly in pgxntool CI against pgxntool-test/master.
This is the rare exception, not the norm.

## Cross-repo reusable workflow — tradeoffs and constraints

The `test` job calls a reusable workflow from pgxntool-test:
```yaml
uses: Postgres-Extensions/pgxntool-test/.github/workflows/run-tests.yml@<ref>
```

GitHub Actions requires the `uses:` ref to be a **static string** — expressions like
`${{ }}` are not supported in the repo/path portion or the `@ref` suffix in practice.

### The @branch → @master ref

While developing on a feature branch where pgxntool-test also has changes, this ref
is set to `@<branch>` so CI can find `run-tests.yml` before it lands on master.

**IMPORTANT**: This ref must be updated to `@master` before pgxntool merges. The
correct merge order is: **pgxntool-test merges first**, then update this ref to
`@master`, then pgxntool merges.

**For Claude**: Do NOT leave a `@<branch>` ref without explicit user approval. The
user merges directly from the PR page — there are no manual steps between merges.
See `.github/workflows/CLAUDE.md` in pgxntool-test for the full picture.

### Changes to run-tests.yml

`run-tests.yml` lives in pgxntool-test and is the single source of truth for all test
steps. If it changes, pgxntool's CI uses `@master` — so it won't see the new version
until pgxntool-test merges. This is acceptable because:
- Changes to `run-tests.yml` require a paired test PR (not commit-with-no-tests)
- When a paired test PR exists, pgxntool's `test` job is skipped anyway
- The two scenarios are mutually exclusive in practice

## Label name

The label `commit-with-no-tests` is defined as a const (`NO_TEST_LABEL`) in `ci.yml`
and as `LABEL` in `protect-label.yml`. The job-level `if:` condition in
`protect-label.yml` must also use the literal string (YAML can't reference JS consts)
— keep these in sync if the label name ever changes.
286 changes: 286 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
name: CI

on:
pull_request:
# We use 'pull_request' (not 'pull_request_target') deliberately.
# 'pull_request_target' runs with write access to the base repo, which is
# a security risk for untrusted fork code. Since this workflow only reads
# from other public repos (no secrets needed), 'pull_request' is correct
# and safe even for fork PRs.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
permissions:
pull-requests: read
checks: read

concurrency:
group: ci-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
check-test-pr:
name: Check for paired pgxntool-test PR
runs-on: ubuntu-latest
# This check polls until the paired pgxntool-test CI run completes
# (up to 20 minutes). The job timeout gives a few minutes of headroom.
timeout-minutes: 25
Comment thread
coderabbitai[bot] marked this conversation as resolved.
outputs:
run-tests: ${{ steps.check.outputs.run_tests }}
test-ref: ${{ steps.check.outputs.test_ref }}

steps:
- name: Find paired pgxntool-test PR or check commit-with-no-tests label
id: check
uses: actions/github-script@v7
Comment thread
jnasbyupgrade marked this conversation as resolved.
with:
# GITHUB_TOKEN is sufficient for reading public repos. If these repos
# are ever made private, replace with a PAT stored as a secret with
# 'repo' scope on both repos. Note: PAT expiration causes silent
# failures here — the API returns 401 and the job errors out instead
# of failing gracefully with a useful message.
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const branch = context.payload.pull_request.head.ref;
const prNumber = context.payload.pull_request.number;
// Single source of truth for the label name. Must also match the
// literal string in the protect-label.yml job-level `if:` condition
// (YAML expressions can't reference JS constants).
const NO_TEST_LABEL = 'commit-with-no-tests';

// master-to-master PRs have no paired test PR by convention.
// Run tests against pgxntool-test/master directly.
//
// If a fork PR's branch is named 'master', that's almost certainly
// a mistake (contributors should use a feature branch), but we
// don't block it — just warn visibly as an annotation on the run.
// Note: pull_request gives a read-only token for fork PRs, so we
// can't post a PR comment back to the upstream repo from here.
if (branch === 'master') {
Comment on lines +42 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate the shortcut on the base branch too.

Line 57 implements this as “head branch is master”, but the comment says this bypass is only for master-to-master PRs. A PR from master into any other base branch will also skip paired-test enforcement and incorrectly force pgxntool-test/master.

Proposed fix
             const branch = context.payload.pull_request.head.ref;
+            const baseBranch = context.payload.pull_request.base.ref;
             const prNumber = context.payload.pull_request.number;
@@
-            if (branch === 'master') {
+            if (branch === 'master' && baseBranch === 'master') {
               const headRepo = context.payload.pull_request.head.repo;
               const isBaseRepo =
                 headRepo?.owner?.login === context.repo.owner &&
                 headRepo?.name === context.repo.repo;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 42 - 57, The current gate only checks
the head branch variable (branch / context.payload.pull_request.head.ref) and
treats any PR with head 'master' as a master-to-master bypass; change this to
require both head and base be 'master' by reading
context.payload.pull_request.base.ref into a variable (e.g., base) and update
the if condition from if (branch === 'master') to if (branch === 'master' &&
base === 'master') so the NO_TEST_LABEL / paired-test logic only applies to true
master-to-master PRs.

const headRepo = context.payload.pull_request.head.repo;
const isBaseRepo =
headRepo?.owner?.login === context.repo.owner &&
headRepo?.name === context.repo.repo;
if (!isBaseRepo) {
core.warning(
`PR head branch is named 'master' but comes from a fork ` +
`(${headRepo?.full_name ?? 'unknown'}). Contributors should ` +
`use a feature branch, not master. Proceeding with tests ` +
`against pgxntool-test/master.`
);
}
core.setOutput('run_tests', 'true');
core.setOutput('test_ref', 'master');
return;
}
Comment on lines +49 to +73
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Restrict the master shortcut to PRs whose head is this repo.

Right now any PR whose source branch is named master hits this early return, including fork PRs. That bypasses the paired pgxntool-test requirement for a real class of contributor PRs.

Proposed fix
-            if (branch === 'master') {
+            const headRepo = context.payload.pull_request.head.repo;
+            const isBaseRepoHead =
+              headRepo?.owner?.login === context.repo.owner &&
+              headRepo?.name === context.repo.repo;
+
+            if (branch === 'master' && isBaseRepoHead) {
               core.setOutput('run_tests', 'true');
               core.setOutput('test_ref', 'master');
               return;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// master-to-master PRs have no paired test PR by convention.
// Run tests against pgxntool-test/master directly.
if (branch === 'master') {
core.setOutput('run_tests', 'true');
core.setOutput('test_ref', 'master');
return;
}
// master-to-master PRs have no paired test PR by convention.
// Run tests against pgxntool-test/master directly.
const headRepo = context.payload.pull_request.head.repo;
const isBaseRepoHead =
headRepo?.owner?.login === context.repo.owner &&
headRepo?.name === context.repo.repo;
if (branch === 'master' && isBaseRepoHead) {
core.setOutput('run_tests', 'true');
core.setOutput('test_ref', 'master');
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 41 - 47, The early return for branch
=== 'master' should only apply when the PR's head is from this repository (not a
fork); update the logic that uses branch to also check the pull request head
repo identity (e.g., compare pull_request.head.repo.full_name or head.repo.name
against the current repository) before calling
core.setOutput('run_tests','true') and core.setOutput('test_ref','master');
ensure forked PRs with head repo different from this repo skip this shortcut so
the paired pgxntool-test requirement still applies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm... it's probably a mistake if someone commits to master on a fork. We shouldn't dis-allow it, but it'd be nice if CI posted a warning.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnasbyupgrade That's a sensible middle ground. Here's a version that posts a warning comment when the branch is master and the head repo is a fork, but still lets CI proceed normally:

             // master-to-master PRs have no paired test PR by convention.
             // Run tests against pgxntool-test/master directly.
             if (branch === 'master') {
+              const headRepo = context.payload.pull_request.head.repo;
+              const isBaseRepoHead =
+                headRepo?.owner?.login === context.repo.owner &&
+                headRepo?.name === context.repo.repo;
+
+              if (!isBaseRepoHead) {
+                await github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: prNumber,
+                  body:
+                    '⚠️ **Heads up:** This PR was opened from the `master` branch of a fork. ' +
+                    'Using `master` as a feature branch is usually a mistake — consider ' +
+                    'creating a dedicated branch for your changes instead. CI will proceed ' +
+                    'normally, but please double-check this is intentional.',
+                });
+              }
+
               core.setOutput('run_tests', 'true');
               core.setOutput('test_ref', 'master');
               return;
             }

One small thing to be aware of: because this uses github.rest.issues.createComment, the pull_request event's GITHUB_TOKEN needs write permission to post comments. The workflow currently doesn't declare permissions: at the job level — you'll either want to add pull-requests: write to the check-test-pr job or at the workflow level:

jobs:
  check-test-pr:
    permissions:
      pull-requests: write

Without that, the comment call will silently fail (or error) on fork PRs since the default pull_request token is read-only for comments from forks.


// The owner of this PR's head repo — the contributor's fork owner
// for fork PRs, or the base repo owner for maintainer PRs.
// The paired pgxntool-test PR must come from the SAME owner.
// We never cross-match PRs across different contributors' forks.
const prOwner = context.payload.pull_request.head.repo?.owner?.login;

// Look for open pgxntool-test PRs with the SAME branch name AND
// the same fork owner. Branch names must match exactly.
//
// The GitHub API's 'head' filter requires "owner:branch" format.
// We list all open PRs and filter locally — safe for repos with
// few open PRs, and avoids needing to know the fork repo name.
// paginate() fetches all pages automatically, so this is correct
// even if pgxntool-test ever exceeds 100 open PRs (the per_page cap).
const prs = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: 'pgxntool-test',
state: 'open',
per_page: 100
});

const matching = prs.filter(pr =>
pr.head.ref === branch &&
pr.head.repo?.owner?.login === prOwner
);
if (matching.length > 1) {
core.setFailed(
`Multiple open pgxntool-test PRs from ${prOwner} match branch ` +
`'${branch}'. Cannot determine which one to use.\n\n` +
`Close all but one, then re-run this check.`
);
return;
}

const testPR = matching.length === 1 ? matching[0] : null;

if (testPR) {
// Error if the no-test label is also set — that's contradictory.
// Re-fetch the PR live (not from payload) in case the label was
// added after this workflow was triggered.
const { data: currentPR } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
if (currentPR.labels.some(l => l.name === NO_TEST_LABEL)) {
core.setFailed(
`PR has the '${NO_TEST_LABEL}' label, but a paired ` +
`pgxntool-test PR #${testPR.number} exists on branch '${branch}'.\n\n` +
`Remove the '${NO_TEST_LABEL}' label — it should only be used ` +
`when there is genuinely no paired test PR.`
);
return;
}

// A paired test PR exists. Verify its CI passed for the exact
// current HEAD SHA and that the run is recent enough to be valid.
const sha = testPR.head.sha;
const testPRUrl =
`https://github.com/${context.repo.owner}/pgxntool-test/pull/${testPR.number}`;
const recheckUrl =
`https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${prNumber}/checks`;

core.info(`Found pgxntool-test PR #${testPR.number} (${sha.slice(0, 7)})`);

// Poll until all check runs for the exact HEAD SHA complete.
// Using 'ref: sha' (not branch name) ensures we only see runs for
// this commit — never stale runs from an older push on the same branch.
//
// We poll rather than fail immediately because both repos are often
// pushed close together. When that happens, pgxntool CI starts while
// pgxntool-test CI may not have queued yet. We wait up to 20 minutes.
const POLL_INTERVAL_MS = 30 * 1000;
const MAX_WAIT_MS = 20 * 60 * 1000;
const waitStart = Date.now();
let runs;

while (true) {
// per_page: 100 is intentional here — a single commit will
// not realistically have 100+ CI check runs, so pagination
// is unnecessary. (pulls.list uses paginate() above because
// an active repo could have many open PRs.)
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: 'pgxntool-test',
ref: sha,
per_page: 100
});
runs = checks.check_runs;

const incomplete = runs.filter(r => r.status !== 'completed');
if (runs.length > 0 && incomplete.length === 0) break;

const elapsed = Date.now() - waitStart;
if (elapsed >= MAX_WAIT_MS) {
const mins = Math.round(elapsed / 60000);
if (runs.length === 0) {
core.setFailed(
`pgxntool-test PR #${testPR.number} has no CI runs for ` +
`SHA ${sha.slice(0, 7)} after waiting ${mins} min.\n\n` +
`Push a commit (or manually re-run CI) on the test PR:\n` +
` Test PR: ${testPRUrl}\n` +
` Re-run this check: ${recheckUrl}`
);
} else {
const names = incomplete.map(r => r.name).join(', ');
core.setFailed(
`pgxntool-test PR #${testPR.number} CI did not finish within ` +
`${mins} min for SHA ${sha.slice(0, 7)}: ${names}\n\n` +
` Test PR: ${testPRUrl}\n` +
` Re-run this check: ${recheckUrl}`
);
}
return;
}

if (runs.length === 0) {
core.info(`No CI runs yet for pgxntool-test PR #${testPR.number} (${sha.slice(0, 7)}); waiting 30s...`);
} else {
const names = incomplete.map(r => r.name).join(', ');
core.info(`pgxntool-test CI still running (${names}); waiting 30s...`);
}
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
}

// All checks complete — look for failures.
// 'success', 'skipped', 'neutral' are non-blocking.
const failed = runs.filter(
r => !['success', 'skipped', 'neutral'].includes(r.conclusion)
);
if (failed.length > 0) {
const names = failed.map(r => `${r.name} (${r.conclusion})`).join(', ');
core.setFailed(
`pgxntool-test PR #${testPR.number} CI failed for ` +
`SHA ${sha.slice(0, 7)}: ${names}\n\n` +
`Fix the test PR CI, then re-run this check:\n` +
` Test PR: ${testPRUrl}\n` +
` Re-run this check: ${recheckUrl}`
);
return;
}

core.info(
`pgxntool-test PR #${testPR.number} CI passed for ` +
`SHA ${sha.slice(0, 7)} — tests run there, not here.`
);
core.setOutput('run_tests', 'false');
core.setOutput('test_ref', sha);
return;
}

// No paired test PR found. Check for the NO_TEST_LABEL label,
// which a maintainer can apply when a pgxntool change genuinely
// needs no test changes (unusual).
//
// We make a live API call rather than reading from the event
// payload. The payload is a snapshot from when this workflow was
// triggered — a maintainer may have added the label after that.
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});

if (pr.labels.some(l => l.name === NO_TEST_LABEL)) {
core.info(
`'${NO_TEST_LABEL}' label is present; running tests ` +
"against pgxntool-test/master. The protect-label workflow " +
"ensures only maintainers can apply this label."
);
core.setOutput('run_tests', 'true');
core.setOutput('test_ref', 'master');
return;
}

// Neither a paired test PR nor the override label was found.
// Fail with a clear, actionable message.
core.setFailed(
`No paired pgxntool-test PR found for branch '${branch}', ` +
`and no '${NO_TEST_LABEL}' label on this PR.\n\n` +
`pgxntool changes should always be paired with matching test\n` +
`changes in pgxntool-test. This check enforces that pairing.\n\n` +
`To resolve:\n` +
` 1. Open a PR in pgxntool-test from a branch ALSO named '${branch}'.\n` +
` Branch names must match exactly for the pairing to work.\n\n` +
Comment on lines +254 to +256
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Mention the same-owner requirement in the failure guidance.

Lines 96-99 require both the same branch name and the same head owner, but this remediation only tells contributors to match the branch. Following it from the wrong fork owner will still fail CI.

✏️ Proposed patch
-              `  1. Open a PR in pgxntool-test from a branch ALSO named '${branch}'.\n` +
-              `     Branch names must match exactly for the pairing to work.\n\n` +
+              `  1. Open a PR in pgxntool-test from the same GitHub owner ` +
+              `('${prOwner}') using a branch ALSO named '${branch}'.\n` +
+              `     Both owner and branch name must match exactly for the pairing to work.\n\n` +
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`To resolve:\n` +
` 1. Open a PR in pgxntool-test from a branch ALSO named '${branch}'.\n` +
` Branch names must match exactly for the pairing to work.\n\n` +
`To resolve:\n` +
` 1. Open a PR in pgxntool-test from the same GitHub owner ('${prOwner}') using a branch ALSO named '${branch}'.\n` +
` Both owner and branch name must match exactly for the pairing to work.\n\n` +
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 254 - 256, Update the CI failure
guidance string that currently reads "To resolve:\n  1. Open a PR in
pgxntool-test from a branch ALSO named '${branch}'..." to also require the same
fork/owner as the head of the current PR; explicitly state that the PR must be
opened from the same repository owner (head) as this PR in pgxntool-test so
branch-name matching alone won't be accepted. Locate and modify the template
string containing `To resolve:\n  1. Open a PR in pgxntool-test from a branch
ALSO named '${branch}'` and append a clear sentence like "The PR must be created
from the same fork/owner (head) as this PR" to ensure both branch name and owner
requirements are communicated.

` 2. If this pgxntool change truly needs no test updates (unusual),\n` +
` ask a maintainer to apply the '${NO_TEST_LABEL}' label.\n` +
` Only maintainers can apply this label. It is not a normal\n` +
` shortcut — most pgxntool changes require test updates.\n\n` +
`See: https://github.com/Postgres-Extensions/pgxntool-test#ci-and-contributing`
);

test:
needs: check-test-pr
if: needs.check-test-pr.outputs.run-tests == 'true'
# -----------------------------------------------------------------------
# CROSS-REPO REUSABLE WORKFLOW — READ BEFORE CHANGING THIS REF
# See: .github/workflows/CLAUDE.md for full architecture notes.
#
# The ref (@add-ci / @master) must be a static string — GitHub Actions
# does not support expressions in uses:. During development on a feature
# branch the ref is @<branch> so CI can find run-tests.yml before it
# lands on master. Before this PR merges, two things must happen:
# 1. pgxntool-test/<branch> merges to master first
# 2. This ref is updated from @<branch> to @master
#
# CURRENT REF: @add-ci (temporary — pgxntool-test/add-ci not yet merged)
# -----------------------------------------------------------------------
uses: Postgres-Extensions/pgxntool-test/.github/workflows/run-tests.yml@add-ci
with:
pgxntool-branch: ${{ github.event.pull_request.head.ref }}
pgxntool-test-ref: master
Comment on lines +264 to +283
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp)"
curl -fsSL \
  https://raw.githubusercontent.com/Postgres-Extensions/pgxntool-test/add-ci/.github/workflows/run-tests.yml \
  -o "$tmp"

echo "=== Permission-sensitive steps in run-tests.yml@add-ci ==="
rg -n "^\s*permissions:|actions/checkout|upload-artifact|download-artifact|github-token|gh api" "$tmp"

Repository: Postgres-Extensions/pgxntool

Length of output: 282


🏁 Script executed:

head -100 .github/workflows/ci.yml | cat -n

Repository: Postgres-Extensions/pgxntool

Length of output: 5374


🏁 Script executed:

curl -fsSL https://raw.githubusercontent.com/Postgres-Extensions/pgxntool-test/add-ci/.github/workflows/run-tests.yml | head -100 | cat -n

Repository: Postgres-Extensions/pgxntool

Length of output: 5206


actions/checkout in the reusable workflow will fail due to insufficient permissions.

The workflow-level permissions block (lines 11–13) includes only pull-requests: read and checks: read, but the called workflow (run-tests.yml@add-ci) uses actions/checkout@v4 at lines 37 and 50, which requires contents: read. Reusable workflows inherit and cannot elevate caller permissions, so both checkouts will fail with a permission error.

Add contents: read to the top-level permissions block, or move the narrow permissions down to check-test-pr and grant test the full scopes it needs.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 280-280: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 264 - 283, The top-level GitHub
Actions permissions are too restrictive for the reusable workflow run-tests.yml
which invokes actions/checkout@v4; update the workflow-level permissions block
to include contents: read (or alternatively move narrow scopes to the
check-test-pr job and give the test job broader scopes) so that the test job
(uses: Postgres-Extensions/pgxntool-test/.github/workflows/run-tests.yml@add-ci)
can successfully run actions/checkout; ensure permissions includes contents:
read for the caller or grant the test job the required scopes.

Loading
Loading