Skip to content

fix(publish): tolerate post-publish provenance flake in npm publish step - #304

Merged
khaliqgant merged 1 commit into
mainfrom
fix/publish-workflow-provenance-flake
Jul 24, 2026
Merged

fix(publish): tolerate post-publish provenance flake in npm publish step#304
khaliqgant merged 1 commit into
mainfrom
fix/publish-workflow-provenance-flake

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Run https://github.com/AgentWorkforce/workforce/actions/runs/30117865418/job/89562902393 published every package — including the agentworkforce umbrella — to npm at 4.1.35, but the job still reported failure.
  • Root cause: npm's --provenance flow makes a second authenticated call after the tarball PUT to attach the attestation. That second call 401'd (Failed to generate Web Auth URLs ... token is invalid) even though the publish itself had already landed on the registry (confirmed via npm view <pkg> version).
  • Because this happened on the last package in the lockstep list, Tag + push and the GitHub Release step never ran, leaving main without the version bump/changelog/tags for an already-shipped 4.1.35.

Fix

  • After npm publish exits non-zero, check the registry for that exact name@version before failing the run. If it's already live, warn and continue instead of aborting the whole multi-package publish. Dry runs are unaffected (nothing to check against, so a failure there still fails hard).

Test plan

  • Reviewed workflow logic by hand against the failed run's logs
  • Next publish run (patch bump) exercises this path for real — the existing "heal to lockstep baseline" step will pick up the already-published 4.1.35 and bump to 4.1.36 cleanly

Review in cubic

Run 30117865418 published every package (incl. the agentworkforce
umbrella) to npm at 4.1.35, but the job still failed because npm's
provenance attestation makes a second authenticated call after the
tarball PUT, and that call 401'd with "Failed to generate Web Auth
URLs" even though the publish itself had already landed. The failure
skipped tag+push and the GitHub Release for an already-shipped version.

Now a non-zero exit from npm publish is checked against the registry
before failing the job: if the version is already live, warn and move
on instead of aborting the whole lockstep run.
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The publish workflow now handles npm publish failures conditionally, verifying real publishes against the npm registry while preserving failures for dry runs and unpublished versions.

Changes

Publish verification

Layer / File(s) Summary
Guarded package publishing
.github/workflows/publish.yml
Failed dry-run publishes abort with an error; real publish failures query the npm registry and either warn when the target version exists or fail when it does not.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: willwashburn, miyaontherelay

Poem

A rabbit packed a tarball tight,
Then checked the registry’s light.
Dry runs fail when things go wrong,
Published versions hop along.
“Verified!” the bunny sings—
Resilient workflows sprout new wings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: handling post-publish provenance flakes during npm publish.
Description check ✅ Passed The description clearly matches the workflow change and explains the registry check fallback behavior.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/publish-workflow-provenance-flake

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.

@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: 343068cb16

ℹ️ 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 on lines +617 to +618
PUBLISHED_VERSION=$(npm view "$NPM_NAME@$VERSION" version 2>/dev/null || true)
if [ "$PUBLISHED_VERSION" = "$VERSION" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify the registry artifact before accepting a failed publish

If another workflow or one-off branch publishes the same name@version after the earlier preflight check (lines 234–252) but before this command, npm publish will correctly fail for a duplicate version, while npm view here returns that version and makes this run continue. The subsequent Tag + push and GitHub Release would then associate the current commit with an artifact produced by the other publisher; compare the packed tarball's integrity (or otherwise prove it is this run's artifact) before treating a nonzero publish as the provenance-only flake.

Useful? React with 👍 / 👎.

@khaliqgant
khaliqgant merged commit 487519c into main Jul 24, 2026
2 of 3 checks passed
@khaliqgant
khaliqgant deleted the fix/publish-workflow-provenance-flake branch July 24, 2026 18:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/workflows/publish.yml:
- Around line 617-623: Replace the registry-existence fallback in the
publish/create-release flow with run-specific serialization or an integrity
check proving this run’s package was published, rather than relying solely on
npm view. Only continue for the known post-publish provenance/attestation
failure after confirming the published tarball or checksum matches this run;
otherwise preserve the failure path and prevent release creation.
- Around line 613-616: Update the shell condition in the publish failure
handling block to read the dry-run value from a step environment variable such
as DRY_RUN, rather than interpolating github.event.inputs.dry_run directly into
Bash source; compare the quoted "$DRY_RUN" value to "true" and configure the
environment assignment through the workflow mechanism.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a43aa1bb-9384-4325-b435-637edee16315

📥 Commits

Reviewing files that changed from the base of the PR and between 0022e15 and 343068c.

📒 Files selected for processing (1)
  • .github/workflows/publish.yml

Comment on lines +613 to +616
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
echo "::error::npm publish failed for $NPM_NAME@$VERSION (dry run)" >&2
exit 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files around workflow =="
git ls-files .github/workflows | sed -n '1,120p'

echo
echo "== targeted publish.yml lines =="
sed -n '200,270p;600,630p' .github/workflows/publish.yml 2>/dev/null | nl -ba | sed -n '1,220p'

echo
echo "== input declarations and step run context =="
rg -n "inputs:|dry_run|if \[ \"\$\{\{ github\.event\.inputs\.dry_run \}\}\"" .github/workflows/publish.yml || true

echo
echo "== static shell injection probe for direct workflow input interpolation =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/publish.yml')
s=p.read_text()
needle='${{ github.event.inputs.dry_run }}'
print('needle_present=', needle in s)
idx=s.find(needle)
context=s[max(0, s.find('run: |',idx-300)):s.find('run: |',idx)+400] if idx!=-1 else ''
print('contains_direct_interpolation_in_run=', needle in context)
print('context_sample_800=', context[:800])
try:
    # Simulate Bash expansion within the quoted test by constructing a value that would break the test.
    # The expression `${{ github.event.inputs.dry_run }}` is not Bash syntax; when replaced with an exploitable input such as:
    #       if [ "$(command_injection)" = "true" ]; then
    # Bash parses command_injection before comparing the command's stdout, which is unsafe regardless of the outer quotes.
    input = '$(rm -rf /)'
    line = f'if [ "{input}" = "true" ]; then'
    print('injected_line=', line)
    print('quoted_preserves_value_in_python_shell=False - this is why it is a shell injection probe, not executable.')
except Exception as e:
    print('probe_error=', e)
PY

Repository: AgentWorkforce/workforce

Length of output: 486


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== publish.yml relevant sections =="
sed -n '220,260p;600,630p' .github/workflows/publish.yml

echo
echo "== input declarations and direct interpolation =="
grep -nE 'inputs:|dry_run|github\.event\.inputs\.dry_run' .github/workflows/publish.yml || true

echo
echo "== step context around publish name =="
grep -n -B10 -A20 'name: Pack + publish|Dry run|dry_run' .github/workflows/publish.yml || true

python3 - <<'PY'
from pathlib import Path
text = Path('.github/workflows/publish.yml').read_text()
lines = text.splitlines()
for i,line in enumerate(lines, 1):
    if 'github.event.inputs.dry_run' in line:
        print(f'direct_input_interpolation_line:{i}:{line}')
PY

Repository: AgentWorkforce/workforce

Length of output: 5983


Do not interpolate dry_run directly into shell source.

${{ github.event.inputs.dry_run }} is expanded before Bash parses the script, so an unsafe input value can break the quoted condition and execute commands on the runner. Pass it through the step environment and compare "$DRY_RUN" instead.

Proposed fix
       - name: Pack + publish
+        env:
+          DRY_RUN: ${{ github.event.inputs.dry_run }}
         run: |
...
-              if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
+              if [ "$DRY_RUN" = "true" ]; then
📝 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
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
echo "::error::npm publish failed for $NPM_NAME@$VERSION (dry run)" >&2
exit 1
fi
- name: Pack + publish
env:
DRY_RUN: ${{ github.event.inputs.dry_run }}
run: |
if [ "$DRY_RUN" = "true" ]; then
echo "::error::npm publish failed for $NPM_NAME@$VERSION (dry run)" >&2
exit 1
fi
🧰 Tools
🪛 zizmor (1.26.1)

[error] 613-613: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/publish.yml around lines 613 - 616, Update the shell
condition in the publish failure handling block to read the dry-run value from a
step environment variable such as DRY_RUN, rather than interpolating
github.event.inputs.dry_run directly into Bash source; compare the quoted
"$DRY_RUN" value to "true" and configure the environment assignment through the
workflow mechanism.

Source: Linters/SAST tools

Comment on lines +617 to +623
PUBLISHED_VERSION=$(npm view "$NPM_NAME@$VERSION" version 2>/dev/null || true)
if [ "$PUBLISHED_VERSION" = "$VERSION" ]; then
echo "::warning::npm publish exited non-zero for $NPM_NAME@$VERSION but the registry already has that version (likely a post-publish attestation flake) - continuing"
else
echo "::error::npm publish failed for $NPM_NAME@$VERSION and the registry does not have it" >&2
exit 1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow file overview =="
wc -l .github/workflows/publish.yml
printf '\n== publish.yml outline / key occurrences =='
rg -n "PUBLISHED_VERSION|npm publish|npm view|unpublished|attestation|release|tag|workflow-run|concurrency|matrix" .github/workflows/publish.yml || true

printf '\n== relevant lines 560-650 =='
sed -n '560,650p' .github/workflows/publish.yml

Repository: AgentWorkforce/workforce

Length of output: 13474


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== concurrency block =="
sed -n '1,80p' .github/workflows/publish.yml

printf '\n== publish job definition around pack+publish tagging =="
sed -n '380,450p' .github/workflows/publish.yml

printf '\n== create-release trigger/outputs wiring =="
sed -n '632,712p' .github/workflows/publish.yml

Repository: AgentWorkforce/workforce

Length of output: 2277


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== publish job inputs/targets =="
sed -n '80,130p' .github/workflows/publish.yml

printf '\n== create-release workflow dependencies =='
sed -n '632,712p' .github/workflows/publish.yml

printf '\n== behavioral probe: compare npm publish exit path and registry check semantics =='
python3 - <<'PY'
from pathlib import Path
import textwrap

p = Path(".github/workflows/publish.yml")
s = p.read_text()

checks = {
    "concurrency_cancel_in_progress_false": "cancel-in-progress: false" in s and "group: publish-${{ github.ref }}" in s,
    "no_pnpm_rsync_mutex": "pnpm-rsync" not in s and "pnpm-lock.yaml" not in s.split("name: Pack + publish")[1].split("name: Tag + push")[0],
    "only_registry_check_on_nonzero": "PUBLISHED_VERSION=$(npm view \"$NPM_NAME@$VERSION\" version 2>/dev/null || true)" in s,
    "no_sha_or_tarball_integrity": "sha256" not in textwrap.dedent(s.split("name: Pack + publish")[1].split("name: Tag + push")[0]) and "npm pack" not in textwrap.dedent(s.split("name: Pack + publish")[1].split("name: Tag + push")[0]),
    "creates_release": "softprops/action-gh-release@example" not in s and "softprops/action-gh-release@v3" in s,
}
for k,v in checks.items():
    print(f"{k}={v}")
PY

Repository: AgentWorkforce/workforce

Length of output: 6346


Do not treat registry existence as proof that this run published the package.

publish/create-release can still proceed after a failed publish if another concurrent or historical run already published npm view "$NPM_NAME@$VERSION" succeeds, then this run tags/releases the same version without this run’s tarball/publish attestation reaching the registry. Add a publish-run serializer or checksum/integrity check, and only tolerate the known post-publish provenance failure.

🤖 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/publish.yml around lines 617 - 623, Replace the
registry-existence fallback in the publish/create-release flow with run-specific
serialization or an integrity check proving this run’s package was published,
rather than relying solely on npm view. Only continue for the known post-publish
provenance/attestation failure after confirming the published tarball or
checksum matches this run; otherwise preserve the failure path and prevent
release creation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant