Skip to content

feat(publish): require a SHA-pinned caller before signing - #858

Merged
devantler merged 9 commits into
mainfrom
claude/actions-pin-publish-caller-2818
Aug 2, 2026
Merged

feat(publish): require a SHA-pinned caller before signing#858
devantler merged 9 commits into
mainfrom
claude/actions-pin-publish-caller-2818

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Why

Our cluster decides whether a first-party image or manifest bundle may run by checking who signed it. That check trusts a signature only if it came from these publish workflows — but it does not care which revision of them. So a caller that invokes a publish workflow by a branch or a moving tag can have an old, superseded revision mint a signature the cluster still accepts.

Every repo that publishes today already pins correctly, but nothing enforces it: the property held by convention only. Measured across the portfolio — all 7 callers pin by commit SHA, and none of the 5 product repos involved runs any workflow-security linting that would catch a regression.

What

The two publish workflows now refuse to run unless the caller pinned them to a commit SHA, checked before they check out or sign anything. Nothing to maintain: no list of approved revisions, and dependency automation already keeps the pins current.

  • Security floor gained: a superseded revision of a publish workflow can no longer mint a trusted signature.
  • Everyday cost: none. All 7 existing callers already pass; a mistake now fails fast at publish time with the exact fix in the message, instead of silently producing an over-trusted signature.

Part of devantler-tech/platform#2818

The publish workflows mint keyless cosign certificates whose SAN records
github.job_workflow_ref, and the cluster's image and artifact trust rules verify
against it. Accepting a branch or a moving tag lets a superseded revision of the
workflow mint a signature the cluster still trusts.

Require the caller to pin by 40-character commit SHA before either workflow
checks out or signs anything, and prove it with a test that executes the guard
script extracted from the workflow against every ref shape.

Part of devantler-tech/platform#2818
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

MegaLinter analysis: Success

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 0.39s
✅ GO revive 2 0 0 9.77s
✅ REPOSITORY betterleaks yes no no 0.46s
✅ REPOSITORY checkov yes no no 19.7s
✅ REPOSITORY gitleaks yes no no 0.08s
✅ REPOSITORY git_diff yes no no 0.02s
✅ REPOSITORY osv-scanner yes no no 0.56s
✅ REPOSITORY secretlint yes no no 0.82s
✅ REPOSITORY syft yes no no 1.78s
✅ REPOSITORY trivy yes no no 13.26s
✅ REPOSITORY trivy-sbom yes no no 0.21s
✅ REPOSITORY trufflehog yes no no 4.68s

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)

  • Documentation: Custom Flavors
  • Command: npx mega-linter-runner@9.6.0 --custom-flavor-setup --custom-flavor-linters COPYPASTE_JSCPD,GO_REVIVE,REPOSITORY_CHECKOV,REPOSITORY_GIT_DIFF,REPOSITORY_GITLEAKS,REPOSITORY_BETTERLEAKS,REPOSITORY_OSV_SCANNER,REPOSITORY_SECRETLINT,REPOSITORY_SYFT,REPOSITORY_TRIVY,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

github.job_workflow_ref is not exposed in the expression context: measured
empty inside a called reusable workflow, while github.workflow_ref resolves.
The first version of this guard would therefore have failed every real publish
while passing CI, because the dry-run test never reaches the publish job.

Resolve the ref from the OIDC token's job_workflow_ref claim instead, which is
the same value Fulcio copies into the certificate SAN the cluster verifies.
The probe established the OIDC claim resolves inside a called reusable workflow
and named the called workflow rather than the caller's entry workflow; its
result is recorded on the pull request.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Verification record

The first version of this guard was wrong, and only an E2E probe could show it

I first read the caller ref from ${{ github.job_workflow_ref }}. actionlint rejected the
property; I could not tell from that alone whether actionlint's context model was simply stale (it
also rejects the valid code-quality permission scope in this repo), so I measured it instead of
picking a side.

A temporary probe job inside publish-app.yaml, exercised by this repo's own dry-run test job:

PROBE job_workflow_ref=[]
PROBE job_workflow_sha=[]
PROBE workflow_ref=[devantler-tech/actions/.github/workflows/ci.yaml@refs/pull/858/merge]

The property is empty. The guard would have evaluated an empty ref, failed closed, and broken
every real publish
— while passing CI, because the dry-run test never enters the publish job.
Local tests could not have caught it either: they set the variable directly.

What the value actually is

Re-probed against the OIDC token, which is where the claim really lives — and which is also the
value Fulcio copies into the certificate SAN that the cluster verifies:

PROBE claim_job_workflow_ref=[devantler-tech/actions/.github/workflows/publish-app.yaml@refs/pull/858/merge]
PROBE claim_workflow_ref=[devantler-tech/actions/.github/workflows/ci.yaml@refs/pull/858/merge]
PROBE extracted=[refs/pull/858/merge]

Two things this settles: the claim names the called workflow (publish-app.yaml) where
workflow_ref names the caller's entry workflow (ci.yaml) — so they are genuinely different
values, and the context property was the wrong one twice over; and the ${REF##*@} extraction
returns the ref correctly against a real token. The probe was removed in 0b4f5bb.

Note this PR-merge ref is a ref the guard rejects, which is correct: a refs/pull/N/merge
caller is not SHA-pinned. It never fires in CI because dry-run skips the publish job.

Over-tightening control

Every repo that calls a publish workflow today, and how it pins: .github 061b345a…,
ascoachingogvaner 625b7c0c…, aws 5aa2657f…, doggy-countdown 9705e47d…, dotnet-template
625b7c0c…, gitops-tenant-template 625b7c0c…, wedding-app 6ae5d87b…. 7 of 7 are 40-hex
SHAs
, so the guard cannot block any correct existing caller — the DevEx cost is zero.

Test and ablations

.github/tests/test-publish-caller-pin.sh executes the guard script extracted from the workflow
(yq-read, never transcribed) against every ref shape: a 40-hex SHA is accepted; refs/heads/main,
refs/tags/v1.2.3, refs/pull/12/merge, @v10, @main, a 39-hex near-miss, an uppercase-hex
near-miss, a 40-hex-plus-suffix, a ref with no @, and an empty value are all rejected.

Each ablation was applied and re-run; every one turns the test red:

Ablation Result
Guard step removed RED — presence assertion
Resolve step removed RED — OIDC-claim assertion
Resolve step reads .workflow_ref instead RED — OIDC-claim assertion
Guard re-bound to ${{ github.job_workflow_ref }} RED — the empty-context assertion
Regex loosened to ^[0-9a-f]+$ RED — the 39-hex near-miss

Two arms initially failed to apply (a perl quoting no-op, and a string that also occurs in the
probe). Both are recorded as untested-then-redone rather than passed, and both fire in the table
above.

Linters

actionlint on the two changed publish workflows: 0 findings, down from 2 with the first
approach (baseline measured in a real .github/workflows/ tree — a flat /tmp path makes actionlint
silently scan nothing and report 0). zizmor --persona regular: no findings. shellcheck on the new
test: clean.

Known limits, stated rather than papered over

  • The guard sits inside the publish job, which is skipped in dry-run, so CI never executes it.
    The static test covers the logic and the ablations prove it discriminates; the OIDC resolution is
    covered by the probe above.
  • If the OIDC endpoint is unavailable the resolve step fails and the publish fails closed. This adds
    no real failure surface: the job already needs OIDC moments later for cosign.
  • The resolve+guard pair is duplicated across the two workflows rather than extracted into a
    composite action. That matches the surrounding code — the existing 🔒 Require a v* tag guard is
    duplicated identically across the same two files — and avoids adding a new public action to the
    shared library's surface for two call sites.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CI is green at 0b4f5bb (137 success, 0 failing, 0 pending). Review focus: the OIDC-claim
resolution in the two publish workflows, and whether the guard's accept/reject set is right.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@devantler: I will review PR #858. I will focus on the OIDC claim resolution and the caller SHA-pin guard.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@devantler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d66a8d92-7047-4c07-a878-ad3b41f19bcb

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4b5f9 and 42bff46.

📒 Files selected for processing (5)
  • .github/tests/test-publish-caller-pin.sh
  • .github/workflows/ci.yaml
  • .github/workflows/publish-app.yaml
  • .github/workflows/publish-manifests.yaml
  • README.md
📝 Walkthrough

Walkthrough

The publish workflows add an opt-in enable-caller-pin input. When enabled, each workflow resolves job_workflow_ref from a Sigstore-audience OIDC token and rejects callers without a 40-character hexadecimal commit SHA. Tests validate workflow structure and guard behavior. CI runs static and live validation jobs.

Possibly related issues

  • devantler-tech/actions#864: This PR introduces the caller-pin guards and tests that the issue proposes making unconditional while removing the feature flag.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: requiring publish workflow callers to use a commit SHA before signing.
Description check ✅ Passed The description explains the security problem, implementation, testing, rollout behavior, and impact of the caller-pin changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with 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.

Inline comments:
In @.github/tests/test-publish-caller-pin.sh:
- Around line 63-76: Add a caller resolve-step index lookup for the step with id
"caller" and assert it is present and precedes guard_index. Update the ordering
assertions around guard_index and checkout_index so the test verifies
caller_index < guard_index while preserving the existing guard-before-checkout
validation.

In @.github/workflows/publish-app.yaml:
- Around line 50-66: Add a bounded curl timeout to the OIDC token request in the
caller step, specifically the curl command assigning token, by including an
appropriate --max-time value while preserving its existing authorization,
audience, and fail-fast behavior.

In @.github/workflows/publish-manifests.yaml:
- Around line 73-86: Document this SHA-pinned caller check as an intentional
security exception to the default-off requirement for new reusable-workflow
behavior, explaining why it must remain unconditional and cannot be safely
disabled. Keep the validation in the “Require a SHA-pinned caller” step
unchanged.
- Around line 56-72: Add a connection and overall execution timeout to the curl
invocation that retrieves the OIDC token in the caller ref resolution step,
ensuring a stalled endpoint fails promptly while preserving the existing token
parsing and output behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a97ff913-a922-470d-923a-87dd29ee4ab4

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4b5f9 and 0b4f5bb.

📒 Files selected for processing (4)
  • .github/tests/test-publish-caller-pin.sh
  • .github/workflows/ci.yaml
  • .github/workflows/publish-app.yaml
  • .github/workflows/publish-manifests.yaml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
.github/workflows/*.yaml

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/*.yaml: Keep all GitHub Actions workflows under .github/workflows/.
Reusable workflows must use the workflow_call trigger.
Pin every remote action reference to a full commit SHA with a version comment; do not use remote self-references.
Include step-security/harden-runner as the first step of every reusable-workflow job, with egress-policy: audit.
Set top-level workflow permissions to {} and grant permissions per job.
Set persist-credentials: false on actions/checkout unless the job must push.
Workflows used as organization-level rulesets must include pull_request and merge_group triggers in addition to workflow_call.
For reusable workflows referencing a sibling action, check out the workflow repository at ${{ job.workflow_sha }} into .devantler-tech-actions, then invoke the action locally; remove the checkout before workspace-wide scans or commits.
New reusable-workflow jobs, steps, or behaviors must be behind a default-off boolean opt-in input and guarded with if: ${{ inputs.<enable-x> }}.
When a workflow supports both workflow_dispatch and workflow_call, normalize boolean inputs with inputs.<enable-x> == true || inputs.<enable-x> == 'true'.
Test both enabled and disabled states of every feature flag with CI test jobs.
Gating reusable workflows must have both a passing self-test and a failing-input self-test that verifies the expected finding; non-gating workflows require happy-path coverage.
Preserve tested consumer contracts, such as validate-go-project.yaml honoring .govulncheck-allow.txt; update the corresponding self-tests whenever the implementation changes.

Files:

  • .github/workflows/publish-manifests.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/publish-app.yaml
.github/workflows/ci.yaml

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/ci.yaml: Add a test job for every action and reusable workflow, using local paths such as uses: ./<action> or uses: ./.github/workflows/<workflow>.yaml.
Wire every new test job into ci-required-checks both through needs: and the job-results input of aggregate-job-checks.

Files:

  • .github/workflows/ci.yaml
.github/tests/**

📄 CodeRabbit inference engine (AGENTS.md)

Place deliberately bad fixtures for gating-workflow tests outside the gate's normal scan scope.

Files:

  • .github/tests/test-publish-caller-pin.sh
🪛 ast-grep (0.45.0)
.github/tests/test-publish-caller-pin.sh

[error] 82-82: A variable, parameter expansion, or command-substitution result is passed as the command string to bash -c / sh -c, so its value is re-parsed by the shell. If any part of that value is attacker-controlled (arguments, environment, file contents, network output), it allows arbitrary command execution. Do not interpolate dynamic data into -c: pass the script as a fixed literal and forward untrusted values as positional arguments (bash -c 'program ""' _ "$value"), invoke the target program directly with proper quoting, or restrict input to a validated allowlist first.
Context: "$script"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(bash-c-variable-injection-bash)

🔇 Additional comments (3)
.github/tests/test-publish-caller-pin.sh (1)

77-113: LGTM!

.github/workflows/ci.yaml (1)

1903-1906: LGTM!

.github/workflows/publish-app.yaml (1)

67-80: 🗄️ Data Integrity & Integration

No change needed for the caller-pin guard.

This step enforces an existing required-call contract and cannot be expressed safely behind a job.workflow_sha/pull_request.event.repository.head_sha equivalent without changing the caller model.

Comment thread .github/tests/test-publish-caller-pin.sh
Comment thread .github/workflows/publish-app.yaml
Comment thread .github/workflows/publish-manifests.yaml
Comment thread .github/workflows/publish-manifests.yaml
Addresses the review: new reusable-workflow behaviour must not go live for every
consumer on merge (AGENTS.md, 'Shipping a new capability behind an opt-in flag').
That applies with extra force here, because the guard only runs on a real publish
and CI exercises these workflows in dry-run only, so a misfire would first surface
at release time.

Also bound the OIDC token request with --max-time so it cannot hang a publish job,
and assert both the flag contract and the resolve-before-guard ordering.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

All four findings from the previous round are fixed and their threads resolved. CI is green at
ae5813c (161 success, 0 failing, 0 pending).

Changes since the reviewed head: the guard now ships behind an opt-in enable-caller-pin input
(default false), the OIDC token request is bounded with --max-time, and the test asserts the flag
contract, the resolve-before-guard ordering, and the timeout.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git diff --find-renames --unified=80 ae5813cb3dd9ed525458a75c6c4fc53744f1c4da^ ae5813cb3dd9ed525458a75c6c4fc53744f1c4da -- \
  .github/workflows/publish-app.yaml \
  .github/workflows/publish-manifests.yaml \
  .github/workflows/ci.yaml \
  .github/tests/test-publish-caller-pin.sh

printf '\n--- workflow outlines ---\n'
ast-grep outline .github/workflows/publish-app.yaml --items all || true
ast-grep outline .github/workflows/publish-manifests.yaml --items all || true
ast-grep outline .github/workflows/ci.yaml --items all || true

printf '\n--- test file ---\n'
cat -n .github/tests/test-publish-caller-pin.sh

printf '\n--- reusable-workflow call sites ---\n'
rg -n -C 4 'uses:\s*.*\.github/workflows/(publish-app|publish-manifests)\.yaml@' .github

Length of output: 31791


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- enable-caller-pin references ---'
rg -n -C 5 --glob '*.y*ml' --glob '*.yaml' 'enable-caller-pin' .

printf '%s\n' '--- publish reusable-workflow references ---'
rg -n -C 8 --glob '*.y*ml' --glob '*.yaml' 'publish-(app|manifests)\.yaml@' .

printf '%s\n' '--- CI workflow ---'
cat -n .github/workflows/ci.yaml

printf '%s\n' '--- repository instructions ---'
if [ -f AGENTS.md ]; then
  cat -n AGENTS.md
else
  printf '%s\n' 'AGENTS.md is absent'
fi

printf '%s\n' '--- workflow input and condition values ---'
for workflow in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml; do
  printf '\n%s\n' "$workflow"
  yq -r '
    .on.workflow_call.inputs["enable-caller-pin"],
    (.jobs[].steps[] | select(.id == "caller" or .name == "🔒 Require a SHA-pinned caller") | {id, name, if})
  ' "$workflow"
done

printf '%s\n' '--- changed files at reviewed head ---'
git diff-tree --no-commit-id --name-status -r ae5813cb3dd9ed525458a75c6c4fc53744f1c4da

Length of output: 1365


🏁 Script executed:

#!/bin/bash
set -u -o pipefail

head='ae5813cb3dd9ed525458a75c6c4fc53744f1c4da'

printf '%s\n' '--- commit identity and available files ---'
git show -s --format='%H%n%P%n%s' "$head"
git ls-tree -r --name-only "$head" -- .github AGENTS.md | sed -n '1,240p'

printf '%s\n' '--- enable-caller-pin references at reviewed head ---'
git grep -n -C 5 -e 'enable-caller-pin' "$head" -- '*.yaml' '*.yml' || true

printf '%s\n' '--- publish reusable-workflow references at reviewed head ---'
git grep -n -E -C 8 'publish-(app|manifests)\.yaml@' "$head" -- '*.yaml' '*.yml' || true

printf '%s\n' '--- CI workflow at reviewed head ---'
git show "${head}:.github/workflows/ci.yaml" | cat -n

printf '%s\n' '--- repository instructions at reviewed head ---'
git show "${head}:AGENTS.md" 2>/dev/null | cat -n || printf '%s\n' 'AGENTS.md is absent at reviewed head'

printf '%s\n' '--- complete changed-file list at reviewed head ---'
git diff-tree --no-commit-id --name-status -r "$head"

printf '%s\n' '--- workflow input and condition values at reviewed head ---'
for workflow in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml; do
  printf '\n%s\n' "$workflow"
  git show "${head}:${workflow}" | yq -r '
    .on.workflow_call.inputs["enable-caller-pin"],
    (.jobs[].steps[] | select(.id == "caller" or .name == "🔒 Require a SHA-pinned caller") | {id, name, if})
  '
done

Length of output: 214684


@devantler: I found one test-coverage issue.

enable-caller-pin: true is never passed through a reusable-workflow [Test] job. The two existing publish workflow tests at .github/workflows/ci.yaml Lines 2267-2288 omit the input and set dry-run: true. Therefore, the publish job skips before either guarded step can run.

.github/tests/test-publish-caller-pin.sh verifies the YAML text, but it does not exercise the public workflow_call interface with the flag enabled. This conflicts with the repository requirement in AGENTS.md Lines 114-126 to test both states of a default-off feature flag.

Add enabled-state test calls for both workflows. The calls can retain dry-run: true to avoid publishing. They should pass enable-caller-pin: true to verify that the reusable workflow accepts the opt-in configuration. Keep the structural test for the guard ordering and SHA acceptance rules.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit is rate-limited at this head — "you've reached your PR review limit, so we couldn't start
this review. Next review available in: 42 minutes"
— and separately chat-routed the command. It
delivered a real review at 0b4f5bb earlier, so this is a quota state, not a lane outage. Recording
cr:no-gate@ae5813c and advancing one lane rather than parking the PR for the window.

Cursor Bugbot is usage-limited portfolio-wide with no stated retry window, so it is a maintainer
escalation rather than a lane to try.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae5813cb3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/tests/test-publish-caller-pin.sh
Comment thread .github/workflows/publish-app.yaml
Comment thread .github/workflows/publish-manifests.yaml
Addresses the Codex round: the enabled state had no standing coverage, the new
input was undocumented, and a ci: commit would not cut a release for consumers
to pin to.

Add a CI job that runs the resolver EXTRACTED from publish-app.yaml against a
real OIDC token and asserts the claim resolves, then asserts the shipped guard
rejects the unpinned refs/pull/N/merge caller a CI run presents — the
failure-mode coverage convention, and the only way to exercise this integration
without a destructive publish. Document enable-caller-pin in both README input
tables.
@devantler devantler changed the title ci(publish): require a SHA-pinned caller before signing feat(publish): require a SHA-pinned caller before signing Aug 2, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

All three Codex findings are fixed and their threads resolved; CI is green at c8eceff (147 checks,
0 failing, 0 pending), including the new [Test] Publish Caller Pin - Live OIDC job.

Since the last CodeRabbit review at 0b4f5bb: the guard ships behind an opt-in enable-caller-pin
input (default false), the OIDC request is bounded with --max-time, the new input is documented
in both README tables, and there is now standing CI coverage that runs the shipped resolver against
a real OIDC token and asserts the guard rejects an unpinned caller.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'
base='0b4f5bb604475f84c62d92f311be19e16dda8a3e'

printf '%s\n' '--- commit and changed files since prior reviewed head ---'
git show -s --format='%H%n%P%n%s' "$head"
git diff --name-status "$base" "$head"

printf '%s\n' '--- full diff since prior reviewed head ---'
git diff --find-renames --unified=100 "$base" "$head" -- \
  .github/workflows/publish-app.yaml \
  .github/workflows/publish-manifests.yaml \
  .github/workflows/ci.yaml \
  .github/tests/test-publish-caller-pin.sh \
  README.md

printf '%s\n' '--- relevant workflow structures ---'
for file in .github/workflows/publish-app.yaml .github/workflows/publish-manifests.yaml .github/workflows/ci.yaml; do
  printf '\n### %s\n' "$file"
  git show "${head}:${file}" | yq -C '.' | sed -n '1,260p'
done

printf '%s\n' '--- caller-pin static test ---'
git show "${head}:.github/tests/test-publish-caller-pin.sh" | cat -n

printf '%s\n' '--- README caller-pin documentation ---'
git show "${head}:README.md" | rg -n -C 8 'enable-caller-pin|publish-app|publish-manifests' || true

printf '%s\n' '--- reusable workflow call sites at current head ---'
git grep -n -E -C 8 'uses:\s*.*\.github/workflows/publish-(app|manifests)\.yaml@' "$head" -- '*.yaml' '*.yml' || true

printf '%s\n' '--- repository instructions ---'
git show "${head}:AGENTS.md" 2>/dev/null | sed -n '1,220p' || true

Length of output: 64103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'

show_range() {
  local path="$1"
  local start="$2"
  local end="$3"
  printf '\n--- %s, lines %s-%s ---\n' "$path" "$start" "$end"
  git show "${head}:${path}" | nl -ba | sed -n "${start},${end}p"
}

show_range ".github/workflows/publish-app.yaml" 1 130
show_range ".github/workflows/publish-manifests.yaml" 1 125
show_range ".github/workflows/ci.yaml" 2240 2395
show_range ".github/workflows/ci.yaml" 3000 3135
show_range ".github/tests/test-publish-caller-pin.sh" 1 210
show_range "AGENTS.md" 90 145
show_range "README.md" 515 635

printf '\n--- all current reusable publish workflow calls ---\n'
git grep -n -E -C 6 'uses:[[:space:]]*.*\.github/workflows/publish-(app|manifests)\.yaml@' \
  "$head" -- '*.yaml' '*.yml' || true

printf '\n--- live OIDC and caller-pin references ---\n'
git grep -n -E -C 4 'test-publish-caller-pin-live|enable-caller-pin|job_workflow_ref|--max-time' \
  "$head" -- '.github/workflows/*.yaml' '.github/tests/*.sh' || true

Length of output: 1440


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='c8eceff3a8165a0c5b524baa2bdf84f294e75758'

show_range() {
  local path="$1"
  local start="$2"
  local end="$3"
  printf '\n--- %s, lines %s-%s ---\n' "$path" "$start" "$end"
  git show "${head}:${path}" | cat -n | sed -n "${start},${end}p"
}

show_range ".github/workflows/publish-app.yaml" 1 130
show_range ".github/workflows/publish-manifests.yaml" 1 125
show_range ".github/workflows/ci.yaml" 2240 2395
show_range ".github/workflows/ci.yaml" 3000 3135
show_range ".github/tests/test-publish-caller-pin.sh" 1 210
show_range "AGENTS.md" 90 145
show_range "README.md" 515 635

printf '\n--- all current reusable publish workflow calls ---\n'
git grep -n -E -C 6 'uses:[[:space:]]*.*\.github/workflows/publish-(app|manifests)\.yaml@' \
  "$head" -- '*.yaml' '*.yml' || true

printf '\n--- live OIDC and caller-pin references ---\n'
git grep -n -E -C 4 'test-publish-caller-pin-live|enable-caller-pin|job_workflow_ref|--max-time' \
  "$head" -- '.github/workflows/*.yaml' '.github/tests/*.sh' || true

Length of output: 73785


@devantler I found one remaining test-coverage issue.

The new Live OIDC job does not call either reusable workflow. It extracts scripts from publish-app.yaml and runs them directly. The existing reusable-workflow tests at .github/workflows/ci.yaml Lines 2267-2277 and Lines 2340-2349 cover only the default-off state.

AGENTS.md Lines 114-126 requires [Test] jobs for both states of an opt-in input. Add safe reusable-workflow test calls for publish-app.yaml and publish-manifests.yaml with:

with:
  dry-run: true
  enable-caller-pin: true

These calls validate the public workflow_call interface. Keep the Live OIDC test for resolver and guard behavior.

You are interacting with an AI system.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

CodeRabbit chat-routed this request rather than queueing a review — an Analysis chain plus
"For best results, initiate chat on the files or code changes", with no review object at the head
and no rate-limit marker this time. Recording cr:no-gate@c8eceff and advancing rather than
re-triggering, since a chat-route does not clear by waiting.

Codex previously reviewed at ae5813c; all three of its findings are fixed and resolved, and the
head has moved, so this is a fresh review of the changes since.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8eceff3a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yaml
The job holds id-token: write and executes the resolver extracted from
publish-app.yaml. On a pull_request the workspace is PR-controlled, so a modified
resolver could mint and exfiltrate an OIDC token — precisely the hazard this
file's header names, and the reason the Zizmor self-test is already push-only.

Gate it to push, matching that precedent. A regression now surfaces post-merge
rather than on the PR; the static contract test still runs in the PR lane.
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Taking ownership of this PR — the previous run's window elapsed at 12:30Z with no live process and
no review requested at the current head.

State verified live at 5db854f: 97 checks success, 44 skipped, 0 failing, 0 pending; 0
unresolved review threads
; no request or progress marker at this head. The one review object
sitting at this commit is a zero-length reply carrier, not a review result.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@devantler: I will review PR #858 at 5db854f2f4608d43126d56eafe8205cab06f12d7.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yaml (1)

2267-2278: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Exercise the enabled state of enable-caller-pin through the public workflow_call interface.

test-publish-app (Line 2267) and test-publish-manifests (Line 2347) call the reusable workflows only with dry-run: true. Neither sets enable-caller-pin: true. The enabled state of the flag is only exercised by directly executing extracted script fragments in test-publish-caller-pin.sh and test-publish-caller-pin-live, never through an actual workflow_call invocation.

Add enable-caller-pin: true to both dry-run calls. The publish job in each reusable workflow is skipped when dry-run: true (job-level if: ${{ !inputs.dry-run }}), so this addition is safe. It validates that the input schema accepts the flag without a typo or type regression, which script-extraction tests cannot catch.

🧪 Proposed diff
   test-publish-app:
     ...
     with:
       app-name: app
       dry-run: true
+      enable-caller-pin: true
   test-publish-manifests:
     ...
     with:
       dry-run: true
+      enable-caller-pin: true

As per coding guidelines, "Test both enabled and disabled states of every feature flag with CI test jobs."

Also applies to: 2347-2357

🤖 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.yaml around lines 2267 - 2278, Update both the
test-publish-app and test-publish-manifests reusable workflow calls to pass
enable-caller-pin: true alongside dry-run: true, exercising the enabled flag
through the public workflow_call interface while preserving their existing
dry-run behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with 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.

Outside diff comments:
In @.github/workflows/ci.yaml:
- Around line 2267-2278: Update both the test-publish-app and
test-publish-manifests reusable workflow calls to pass enable-caller-pin: true
alongside dry-run: true, exercising the enabled flag through the public
workflow_call interface while preserving their existing dry-run behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7dd2cf31-531f-4ddf-946d-f34996a5cb33

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4f5bb and 5db854f.

📒 Files selected for processing (5)
  • .github/tests/test-publish-caller-pin.sh
  • .github/workflows/ci.yaml
  • .github/workflows/publish-app.yaml
  • .github/workflows/publish-manifests.yaml
  • README.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
.github/tests/**

📄 CodeRabbit inference engine (AGENTS.md)

Place deliberately bad fixtures for gating-workflow tests outside the gate's normal scan scope.

Files:

  • .github/tests/test-publish-caller-pin.sh
.github/workflows/*.yaml

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/*.yaml: Keep all GitHub Actions workflows under .github/workflows/.
Reusable workflows must use the workflow_call trigger.
Pin every remote action reference to a full commit SHA with a version comment; do not use remote self-references.
Include step-security/harden-runner as the first step of every reusable-workflow job, with egress-policy: audit.
Set top-level workflow permissions to {} and grant permissions per job.
Set persist-credentials: false on actions/checkout unless the job must push.
Workflows used as organization-level rulesets must include pull_request and merge_group triggers in addition to workflow_call.
For reusable workflows referencing a sibling action, check out the workflow repository at ${{ job.workflow_sha }} into .devantler-tech-actions, then invoke the action locally; remove the checkout before workspace-wide scans or commits.
New reusable-workflow jobs, steps, or behaviors must be behind a default-off boolean opt-in input and guarded with if: ${{ inputs.<enable-x> }}.
When a workflow supports both workflow_dispatch and workflow_call, normalize boolean inputs with inputs.<enable-x> == true || inputs.<enable-x> == 'true'.
Test both enabled and disabled states of every feature flag with CI test jobs.
Gating reusable workflows must have both a passing self-test and a failing-input self-test that verifies the expected finding; non-gating workflows require happy-path coverage.
Preserve tested consumer contracts, such as validate-go-project.yaml honoring .govulncheck-allow.txt; update the corresponding self-tests whenever the implementation changes.

Files:

  • .github/workflows/publish-manifests.yaml
  • .github/workflows/publish-app.yaml
  • .github/workflows/ci.yaml
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Prefer additive, backward-compatible changes because composite actions and reusable workflows affect every consumer repository; prominently call out deliberate breaking input/output changes.
Use Conventional Commit types for changes: feat for minor releases, fix/perf for patch releases, and breaking-change notation for major releases; use fix or feat when consumers must receive a workflow or action change promptly.

Files:

  • README.md
.github/workflows/ci.yaml

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/ci.yaml: Add a test job for every action and reusable workflow, using local paths such as uses: ./<action> or uses: ./.github/workflows/<workflow>.yaml.
Wire every new test job into ci-required-checks both through needs: and the job-results input of aggregate-job-checks.

Files:

  • .github/workflows/ci.yaml
🔇 Additional comments (5)
.github/workflows/publish-app.yaml (1)

19-23: LGTM!

Also applies to: 55-72, 73-87

.github/workflows/publish-manifests.yaml (1)

26-30: LGTM!

Also applies to: 61-78, 79-93

README.md (1)

411-411: LGTM!

Also applies to: 452-452

.github/tests/test-publish-caller-pin.sh (1)

63-99: LGTM!

Also applies to: 113-114

.github/workflows/ci.yaml (1)

2279-2346: LGTM!

Also applies to: 3108-3108, 3205-3205

The dry-run callers passed only dry-run: true, so the flag's enabled state was
reached only by executing script fragments extracted from the reusable workflows
-- never through a real workflow_call. Passing enable-caller-pin: true proves the
input schema accepts the name and type.

The guard itself still does not run here: yq confirms both guard steps sit inside
jobs gated on `if: ${{ !inputs.dry-run }}`, so a dry-run call skips them. The
enabled BEHAVIOUR stays covered by test-publish-caller-pin.sh and the push-only
live OIDC job.

Ablated: typing the input name wrong makes actionlint report
"input ... is not defined in ... reusable workflow" (7 -> 8 findings); a wrong
TYPE is not caught by actionlint, so that half is covered at workflow_call time
by these jobs. actionlint is 7 on origin/main, 7 at the previous head and 7 here
-- none introduced.

Part of #2818
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Round 5 (CodeRabbit @ 5db854f) — 🟠 Major body finding: VALID, fixed in f331ceb

⚠️ Outside diff range comments (1) — the enabled state of enable-caller-pin was reached only by
executing script fragments extracted from the reusable workflows, never through a real
workflow_call. Both dry-run callers now pass enable-caller-pin: true.

Verified its safety claim structurally rather than taking the prose. Parsing both reusable
workflows shows the two guard steps sit inside a job gated on if: ${{ !inputs.dry-run }}:

publish-app.yaml         job=publish            gate=${{ !inputs.dry-run }}
publish-manifests.yaml   job=publish-manifests  gate=${{ !inputs.dry-run }}
  guard steps: "🔎 Resolve the calling ref from the OIDC claim", "🔒 Require a SHA-pinned caller"

So a dry-run call cannot reach the guard — which matters, because ci.yaml calls these by local
path (./.github/workflows/publish-app.yaml), not by a 40-hex SHA. Had the guard run, it would
have correctly rejected its own CI and turned the build red.

One correction, in the finding's favour and against it

Ablated the claim that this catches "a typo or type regression":

arm actionlint
as written 7 findings
input name typoed (enable-caller-pinn) 8input "enable-caller-pinn" is not defined in "./.github/workflows/publish-app.yaml" reusable workflow
input type wrong ("yes-please" for a type: boolean) 7 — not caught

So the name half is caught statically, before CI even runs; the type half is not caught by
actionlint and is covered at workflow_call dispatch time by these very jobs. Both halves are real,
but they are enforced at different layers and it is worth saying which is which.

actionlint: 7 on origin/main, 7 at the previous head, 7 here — none introduced.

The 🔇 Additional comments (5) section is CodeRabbit's non-actionable category and carries no
finding. Zero unresolved threads.

Restarting the ordered review loop at CodeRabbit against the new head — every push stales the green.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Restarting the ordered loop at this head — the round-5 fix moved the head, so the previous result is
stale.

State at f331ceb: 96 checks success, 42 skipped, 0 pending, 0 failing; mergeStateStatus
CLEAN; 0 unresolved threads. The round-5 resolution record, including the ablation that
splits the finding's "typo or type" claim across two enforcement layers, is in the comment above.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@devantler: I will review the changes at f331ceb.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

cr:no-gate@f331ceb — CodeRabbit is rate-limited, not silent.

There is no review object at this head; the newest sits at the previous head 5db854f
(12:16:37Z). What did change is the auto-generated summary, updated 14 seconds after the request —
and its body carries <!-- auto-generated comment: rate limited by coderabbit.ai --> with a
"Review limit reached" warning. That is service state, not a verdict, so it neither satisfies
the review gate nor blocks this PR.

Advancing to Codex per lane priority. Cursor Bugbot remains usage-limited portfolio-wide with no
retry window, so if Codex also fails to deliver here, all three lanes will have been tried and a
local review round becomes the qualifying fallback.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f331cebe9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yaml
The previous commit set enable-caller-pin: true on both existing dry-run callers,
which added the enabled state and silently removed the only default-off coverage
of the workflow_call interface -- the repository requires both states be tested.
Codex caught it as a P2 on the exact line the previous fix added.

Restored by splitting rather than swapping: test-publish-app and
test-publish-manifests go back to OMITTING the input, which is the default-off
half and also proves the input is genuinely optional; two new sibling jobs pass
it explicitly for the enabled half. Both new jobs are wired into
ci-required-checks' needs list AND its results block -- derived structurally, not
by eye: 87 needs, 87 result references, zero on either side alone.

Neither new job runs the guard: publish-app.yaml and publish-manifests.yaml both
gate their publish job on `if: ${{ !inputs.dry-run }}`. The enabled BEHAVIOUR
stays covered by test-publish-caller-pin.sh and the push-only live OIDC job.

test-publish-caller-pin.sh and test-ci-merge-group-isolation.sh both pass;
actionlint 7, unchanged from origin/main.

Part of #2818
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Round 6 (Codex @ f331ceb) — P2 VALID, fixed in 42bff46

Correct, and it caught the exact hazard my own notes warn about: a fix for a reviewer's finding is
new code.
Round 5's fix set enable-caller-pin: true on both existing dry-run callers, which added
the enabled state and, in the same stroke, removed the only default-off coverage of the
workflow_call interface. Before that commit both callers omitted the input, so the disabled state
was the one actually covered.

Fixed by splitting rather than swapping. test-publish-app and test-publish-manifests go back
to omitting the input — that is the default-off half, and omission additionally proves the input
is genuinely optional rather than merely accepted. Two new sibling jobs,
test-publish-app-caller-pin and test-publish-manifests-caller-pin, pass it explicitly.

job reusable workflow enable-caller-pin
test-publish-app publish-app.yaml omitted
test-publish-app-caller-pin publish-app.yaml true
test-publish-manifests publish-manifests.yaml omitted
test-publish-manifests-caller-pin publish-manifests.yaml true

The second half of the fix, which the finding did not ask for

A new job is only a test if something fails when it fails. ci-required-checks aggregates every
test job in both a needs: list and a results block, and a job missing from either is a job
whose failure passes. Both new jobs are wired into both — verified by deriving the two sets from the
parsed workflow rather than reading them: 87 needs, 87 result references, zero on either side
alone.

Neither new job runs the guard itself: both reusable workflows gate their publish job on
if: ${{ !inputs.dry-run }}. These remain schema checks; the enabled behaviour stays covered by
test-publish-caller-pin.sh and the push-only live OIDC job.

test-publish-caller-pin.sh and test-ci-merge-group-isolation.sh both pass. actionlint is 7,
unchanged from origin/main.

Restarting the ordered loop at CodeRabbit — though note it returned Review limit reached on this
PR at 12:29Z and again on platform#2900 at 12:39Z, so the org quota is currently exhausted; if it
answers the same way the loop records cr:no-gate and advances to Codex again.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Ownership: the tick-904 window on this PR expired at 14:00Z with no request at the current head; taking it over until 15:00Z.

State at 42bff46: CI 96 success / 44 skipped / 0 pending / 0 failing, mergeStateStatus: CLEAN, 0 of 9 review threads unresolved. The round-6 Codex P2 (default-off coverage deleted by the round-5 fix) is fixed here, so every prior review artifact is stale — restarting the ordered provider loop at lane priority 1.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@devantler: I will review the changes at 42bff46. I will verify the default-off and enabled-state workflow_call coverage, the required-check aggregation, and the caller-pin guard behavior.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Escalating to a full review — the incremental pass wedged. The 14:00:51Z request was acked 12 seconds later ("I will review the changes at 42bff46"), but 19 minutes on there is no artifact at this head on any surface: no review object, no inline comment, no summary update. The ack itself names the cause — "CodeRabbit is an incremental review system and does not re-review already reviewed commits" — and this head's ancestors 5db854f and f331ceb were both already reviewed.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

@devantler: I will run a full review at 42bff46cf54842ffb5b37188229dc56c2dc6a1f6.

🐇🔎

✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 4 minutes.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Re-triggering — the 14:19:57Z attempt was quota-blocked, not wedged after all. Its <details> body reported "Your included review limit is currently reached … Your next included review will be available in 4 minutes", while the visible headline read "✅ Full review finished". No artifact landed at this head on any surface, so the headline was not the outcome.

That window expired at ~14:24Z, and an included review was consumed on platform#2900 at 14:26:38Z, so the budget should have rolled over again by now.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Your plan includes PR reviews subject to rate limits. Reviews are available now.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

cr:no-gate@42bff46 — recording CodeRabbit as not delivering at this head, and advancing the ordered loop. Three attempts, none producing an artifact on any surface:

Time Trigger Outcome
14:00:51Z review acked in 12 s, no quota notice, zero artifacts in 19 min
14:19:46Z full review headline "✅ Full review finished", but its <details> said "included review limit is currently reached … available in 4 minutes"quota
14:31:38Z full review "subject to rate limits. Reviews are available now."no artifact in 16 min

The middle one is why this took three attempts to call: a quota block and a wedge look identical from the visible headline, and only the <details> body distinguishes them. The third attempt was posted after the budget had demonstrably rolled over (an included review was served on platform#2900 at 14:26Z), so quota does not explain this one.

Advancing to lane 2 rather than spending a fourth CodeRabbit trigger. Bugbot remains usage-limited portfolio-wide with no retry window, so it is escalation-only and not a third option here.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 42bff46cf5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

Readiness condition 3 — re-recorded at 42bff46

The earlier verification record (10:25Z) was taken at 0b4f5bb6, four heads ago — before the round-5 and round-6 fixes changed which caller jobs exist and what they pass. It does not cover this head, so it is re-established here rather than inherited.

What ran at this exact head:

  • lint-ci-coverage-parity — success. This is the job that executes .github/tests/test-publish-caller-pin.sh, the guard's own logic. Its result is the direct evidence that the contract still holds.
  • All four workflow_call dispatches occurred, covering both flag states. Check-runs exist at this commit for [Test] Publish App - Dry Run, [Test] Publish App - Dry Run (caller-pin enabled), [Test] Publish Manifests - Dry Run, and [Test] Publish Manifests - Dry Run (caller-pin enabled). A check-run for the reusable workflow's inner job only exists if the reusable workflow was actually dispatched, so their presence is the evidence that the input schema accepts enable-caller-pin by name and type in both the omitted and the true state — which is precisely what round 6 restored after the round-5 fix had deleted the default-off half.
  • CI - Required Checks — success, with 96 successful and 0 failing check-runs.

Read the skipped entries correctly — they are by design, not missing coverage. Each of those four callers reports its inner publish job as skipped because publish-app.yaml / publish-manifests.yaml gate that job on if: ${{ !inputs.dry-run }}. The dry-run callers are not meant to publish; their value is the dispatch itself, and the file says so in the comment at ci.yaml:2294-2297.

The one thing not exercisable here, stated plainly: [Test] Publish Caller Pin - Live OIDC is gated on github.event_name == 'push' (ci.yaml:2317), so it cannot run on a pull request at any head — it runs after this merges. That gating is deliberate and predates this change; the in-PR lane covers the guard's logic through the static contract test above. So condition 3 rests on the static contract test plus the both-states dispatch, and the live OIDC leg is a post-merge verification rather than a pre-merge one.

@devantler
devantler marked this pull request as ready for review August 2, 2026 14:57
@devantler
devantler merged commit 36171a3 into main Aug 2, 2026
141 checks passed
@devantler
devantler deleted the claude/actions-pin-publish-caller-2818 branch August 2, 2026 14:57
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant