Skip to content

fix: make release workflow idempotent for already-published crates#160

Merged
ElFantasma merged 1 commit intomainfrom
fix/release-idempotent
Mar 13, 2026
Merged

fix: make release workflow idempotent for already-published crates#160
ElFantasma merged 1 commit intomainfrom
fix/release-idempotent

Conversation

@ElFantasma
Copy link
Copy Markdown
Collaborator

Summary

  • Skip "already exists" errors during cargo publish so re-runs succeed
  • Real errors (permission denied, network, etc.) still fail the workflow

Needed because spawned-macros 0.5.0 was published manually, so the release workflow fails when trying to publish it again.

@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

Review: .github/workflows/release.yaml

The changes look good for handling idempotent publishing. However, there are a few improvements that could be made:

Issues Found:

  1. Race condition on crates.io indexing (lines 27, 37): The 30-second sleep is arbitrary and may not be sufficient for crates.io indexing. Consider using a retry loop with exponential backoff instead.

  2. Missing dependency check: The workflow doesn't verify that dependencies are available before attempting to publish. For example, spawned-rt depends on spawned-macros, but there's no verification that the macros package is actually indexed.

  3. Error handling improvement: The grep pattern "already exists" might be too broad and could miss other "already published" error messages. Consider using a more specific pattern like "is already uploaded".

Suggested improvements:

# Add a retry mechanism for checking package availability
- name: Wait for spawned-macros availability
  run: |
    for i in {1..10}; do
      if cargo search spawned-macros | grep -q "^spawned-macros ="; then
        echo "spawned-macros is available"
        break
      fi
      echo "Waiting for spawned-macros to be available... (attempt $i/10)"
      sleep 10
    done

The current approach is acceptable for a release workflow, but implementing the retry mechanism would make it more robust against crates.io's eventual consistency.


Automated review by Kimi (Moonshot AI) · custom prompt

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

PR #160 Review: fix: make release workflow idempotent for already-published crates

Summary: CI/CD workflow change only — no Rust code touched. The fix adds error filtering to cargo publish steps so re-runs don't fail on already-published crates.


Correctness

Bug: Output is silently swallowed on success. The pattern OUTPUT=$(cmd 2>&1) || { ... } captures stdout+stderr into $OUTPUT, but on success $OUTPUT is never printed. This hides useful cargo publish output (upload progress, warnings) from the CI log. Consider:

OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1)
STATUS=$?
echo "$OUTPUT"
if [ $STATUS -ne 0 ] && ! echo "$OUTPUT" | grep -q "already exists"; then
  exit 1
fi

Or more simply, use the || true-with-post-check idiom.

Potential race: already exists is a substring match. If a future error message happens to contain the string "already exists" for an unrelated reason, the step will silently pass. The actual crates.io error is error: crate version ... is already uploaded. Tightening to grep -q "is already uploaded" is safer.

sleep 30 is not skipped when the crate was already published. If spawned-macros and spawned-rt are already up, the two 30-second sleeps still run unnecessarily. This is minor but wasteful on idempotent re-runs.


Design / Alternatives

A cleaner, more idiomatic approach is --no-verify isn't available here, but cargo publish does support --dry-run. More relevantly, you could check whether the version is already published before attempting:

- name: Publish spawned-macros
  run: |
    if cargo search spawned-macros --limit 1 | grep -q '"0.5.0"'; then
      echo "Already published, skipping"
    else
      cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }}
    fi

This is more explicit and avoids relying on error-message text matching. However, this adds a cargo search dependency and latency, so the current approach is reasonable pragmatically.


Summary

Correctness Minor: success output is silenced; substring match could be fragile
Safety Low risk — worst case is masking a real error with a misleading message
Idempotency goal Achieved for the stated use case

The PR solves the immediate problem. I'd recommend the two small fixes: print $OUTPUT unconditionally and tighten the grep pattern to "is already uploaded".


Automated review by Claude (Anthropic) · custom prompt

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

Findings:

  1. Medium: error classification is too broad and can hide real publish failures
    At .github/workflows/release.yaml:22, .github/workflows/release.yaml:36, and .github/workflows/release.yaml:50, the workflow treats any failure containing "already exists" as non-fatal. That string match is not specific to the duplicate-version case and could mask unrelated errors.
    Suggestion: match a stricter crates.io duplicate-version pattern (or check for the exact known publish error text) before continuing.

  2. Low: successful publish logs are suppressed
    At .github/workflows/release.yaml:21, .github/workflows/release.yaml:35, and .github/workflows/release.yaml:49, command output is captured into OUTPUT and never printed on success, reducing release traceability/debuggability.
    Suggestion: either stream output directly, or echo "$OUTPUT" on success too.

No actor-runtime/concurrency lifecycle concerns from this PR because it only changes CI release workflow logic.


Automated review by OpenAI Codex · custom prompt

@ElFantasma ElFantasma merged commit a6cc1e4 into main Mar 13, 2026
10 checks passed
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 13, 2026

Greptile Summary

This PR updates .github/workflows/release.yaml to make the release workflow idempotent: each of the three cargo publish steps now captures combined stdout/stderr and skips the failure if the output contains the string "already exists", while still propagating all other errors.

The motivation is sound — spawned-macros 0.5.0 was published manually, causing subsequent workflow runs to fail. The shell pattern used (OUTPUT=$(cmd 2>&1) || { ... }) is correct. However, the key concern is:

  • Potential grep mismatch: The actual crates.io error for a duplicate publish typically reads "crate version \name@version` is already uploaded to crates.io"(i.e., **"already uploaded"**, not **"already exists"**). If the grep pattern never matches, theelsebranch executes on every re-run and the workflow continues to fail — the fix would be a no-op. The pattern should be verified against the real failure output and ideally broadened togrep -qE "already uploaded|already exists"`.
  • All three publish steps (spawned-macros, spawned-rt, spawned-concurrency) share the same pattern and the same concern.

Confidence Score: 2/5

  • Not safe to merge as-is: the grep pattern "already exists" likely does not match the actual crates.io error message, making the idempotency fix potentially non-functional.
  • The fix's core mechanism — grep -q "already exists" — may never match the real crates.io error, which typically says "already uploaded to crates.io". If so, every re-run still fails at exit 1, meaning the stated goal of the PR is not achieved. The pattern should be verified and likely broadened before merging.
  • .github/workflows/release.yaml — all three publish steps share the potentially mismatched grep pattern.

Important Files Changed

Filename Overview
.github/workflows/release.yaml Adds "already exists" error-skipping logic to all three cargo publish steps; the grep pattern may not match the actual crates.io error message ("already uploaded"), which would cause the idempotency fix to silently not work.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Release Triggered]) --> B[cargo publish --package spawned-macros]
    B -->|exit 0| C[Output captured, success silent]
    B -->|exit non-zero| D{OUTPUT contains\n'already exists'?}
    D -->|yes| E[Skip – already published\ncontinue]
    D -->|no| F[Print OUTPUT\nexit 1 – fail]
    C --> G[sleep 30s]
    E --> G
    G --> H[cargo publish --package spawned-rt]
    H -->|exit 0| I[Output captured, success silent]
    H -->|exit non-zero| J{OUTPUT contains\n'already exists'?}
    J -->|yes| K[Skip – already published\ncontinue]
    J -->|no| L[Print OUTPUT\nexit 1 – fail]
    I --> M[sleep 30s]
    K --> M
    M --> N[cargo publish --package spawned-concurrency]
    N -->|exit 0| O[Output captured, success silent]
    N -->|exit non-zero| P{OUTPUT contains\n'already exists'?}
    P -->|yes| Q[Skip – already published\ncontinue]
    P -->|no| R[Print OUTPUT\nexit 1 – fail]
    O --> S([Done])
    Q --> S

    style D fill:#ffe0b2
    style J fill:#ffe0b2
    style P fill:#ffe0b2
    style F fill:#ffcdd2
    style L fill:#ffcdd2
    style R fill:#ffcdd2
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 36

Comment:
**Same grep pattern concern as `spawned-macros` step**

The `"already exists"` pattern here has the same potential mismatch issue as described at line 22. If the crates.io error says "already uploaded", this check will silently fail and the step will always exit with error 1 on re-runs.

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 50

Comment:
**Same grep pattern concern as `spawned-macros` step**

Same potential mismatch: `"already exists"` vs `"already uploaded"`. If this doesn't match, `spawned-concurrency` re-runs will always fail.

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 21-28

Comment:
**Successful publish output is silently discarded**

When `cargo publish` exits 0, the content captured in `OUTPUT` is never printed, so workflow logs give no visibility into what was published. Consider echoing the output on success for observability:

```suggestion
          OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) && echo "$OUTPUT" || {
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
              echo "spawned-macros already published, continuing"
            else
              echo "$OUTPUT"
              exit 1
            fi
          }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 22

Comment:
**Grep pattern may not match actual crates.io error message**

The error string `"already exists"` likely does not match what `cargo publish` actually outputs when a crate version is already published. The crates.io API returns:

```
crate version `spawned-macros@0.5.0` is already uploaded to crates.io
```

This contains **"already uploaded"**, not **"already exists"**. If the grep pattern never matches, the `else` branch will always execute on re-runs — printing the output and calling `exit 1` — which defeats the entire purpose of this PR.

The same pattern is repeated for `spawned-rt` (line 36) and `spawned-concurrency` (line 50). Please verify the exact error message against the real failure and consider broadening the match to cover both possible phrasings:

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 1b2a4c9

run: cargo publish --package spawned-rt --token ${{ secrets.CRATES_IO_TOKEN }}
run: |
OUTPUT=$(cargo publish --package spawned-rt --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) || {
if echo "$OUTPUT" | grep -q "already exists"; then
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.

Same grep pattern concern as spawned-macros step

The "already exists" pattern here has the same potential mismatch issue as described at line 22. If the crates.io error says "already uploaded", this check will silently fail and the step will always exit with error 1 on re-runs.

Suggested change
if echo "$OUTPUT" | grep -q "already exists"; then
if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 36

Comment:
**Same grep pattern concern as `spawned-macros` step**

The `"already exists"` pattern here has the same potential mismatch issue as described at line 22. If the crates.io error says "already uploaded", this check will silently fail and the step will always exit with error 1 on re-runs.

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

run: cargo publish --package spawned-concurrency --token ${{ secrets.CRATES_IO_TOKEN }}
run: |
OUTPUT=$(cargo publish --package spawned-concurrency --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) || {
if echo "$OUTPUT" | grep -q "already exists"; then
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.

Same grep pattern concern as spawned-macros step

Same potential mismatch: "already exists" vs "already uploaded". If this doesn't match, spawned-concurrency re-runs will always fail.

Suggested change
if echo "$OUTPUT" | grep -q "already exists"; then
if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 50

Comment:
**Same grep pattern concern as `spawned-macros` step**

Same potential mismatch: `"already exists"` vs `"already uploaded"`. If this doesn't match, `spawned-concurrency` re-runs will always fail.

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +21 to +28
OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) || {
if echo "$OUTPUT" | grep -q "already exists"; then
echo "spawned-macros already published, continuing"
else
echo "$OUTPUT"
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.

Successful publish output is silently discarded

When cargo publish exits 0, the content captured in OUTPUT is never printed, so workflow logs give no visibility into what was published. Consider echoing the output on success for observability:

Suggested change
OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) || {
if echo "$OUTPUT" | grep -q "already exists"; then
echo "spawned-macros already published, continuing"
else
echo "$OUTPUT"
exit 1
fi
}
OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) && echo "$OUTPUT" || {
if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
echo "spawned-macros already published, continuing"
else
echo "$OUTPUT"
exit 1
fi
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 21-28

Comment:
**Successful publish output is silently discarded**

When `cargo publish` exits 0, the content captured in `OUTPUT` is never printed, so workflow logs give no visibility into what was published. Consider echoing the output on success for observability:

```suggestion
          OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) && echo "$OUTPUT" || {
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
              echo "spawned-macros already published, continuing"
            else
              echo "$OUTPUT"
              exit 1
            fi
          }
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

run: cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }}
run: |
OUTPUT=$(cargo publish --package spawned-macros --token ${{ secrets.CRATES_IO_TOKEN }} 2>&1) || {
if echo "$OUTPUT" | grep -q "already exists"; then
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.

Grep pattern may not match actual crates.io error message

The error string "already exists" likely does not match what cargo publish actually outputs when a crate version is already published. The crates.io API returns:

crate version `spawned-macros@0.5.0` is already uploaded to crates.io

This contains "already uploaded", not "already exists". If the grep pattern never matches, the else branch will always execute on re-runs — printing the output and calling exit 1 — which defeats the entire purpose of this PR.

The same pattern is repeated for spawned-rt (line 36) and spawned-concurrency (line 50). Please verify the exact error message against the real failure and consider broadening the match to cover both possible phrasings:

Suggested change
if echo "$OUTPUT" | grep -q "already exists"; then
if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yaml
Line: 22

Comment:
**Grep pattern may not match actual crates.io error message**

The error string `"already exists"` likely does not match what `cargo publish` actually outputs when a crate version is already published. The crates.io API returns:

```
crate version `spawned-macros@0.5.0` is already uploaded to crates.io
```

This contains **"already uploaded"**, not **"already exists"**. If the grep pattern never matches, the `else` branch will always execute on re-runs — printing the output and calling `exit 1` — which defeats the entire purpose of this PR.

The same pattern is repeated for `spawned-rt` (line 36) and `spawned-concurrency` (line 50). Please verify the exact error message against the real failure and consider broadening the match to cover both possible phrasings:

```suggestion
            if echo "$OUTPUT" | grep -qE "already uploaded|already exists"; then
```

How can I resolve this? If you propose a fix, please make it concise.

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