From 587fbeabd836739bcc3366523e4ff195aa2e1074 Mon Sep 17 00:00:00 2001 From: Andrew Altshuler Date: Sat, 23 May 2026 14:19:17 +0100 Subject: [PATCH] ci(publish-crates): set User-Agent + treat "already exists" as success (#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes uncovered while recovering the v0.5.0 publish. 1. crates.io API requires a User-Agent header. The `publish_if_new` skip check was doing a bare `curl -fsSL https://crates.io/api/...` which crates.io rejects with HTTP 403. With `-f` curl exits non-zero, the pipeline returns empty, the script doesn't recognize already-published crates, and we fall through to a real publish attempt. On a re-run that means cargo publish errors with "already exists on crates.io index" for crates that DID publish successfully on the previous run. Fix: send a `User-Agent: ModernRelay-omnigraph-ci (URL)` header. 2. Defense in depth: even with the UA, the API could hiccup. If the skip check misses an existing version and cargo publish errors with "already exists on crates.io index", treat as success instead of failing the whole run. This makes the workflow re-runnable after any partial publish without needing manual intervention. Both fixes are required to recover from the v0.5.0 partial publish where omnigraph-compiler@0.5.0 made it through but the run failed before omnigraph-policy / engine / server / cli — re-triggering the workflow now succeeds end-to-end. Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/publish-crates.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml index fad3951..9484b98 100644 --- a/.github/workflows/publish-crates.yml +++ b/.github/workflows/publish-crates.yml @@ -80,8 +80,15 @@ jobs: version=$(cargo metadata --format-version=1 --no-deps \ | jq -r --arg c "$crate" '.packages[] | select(.name==$c) | .version') + # crates.io API requires a User-Agent header — without it the + # API responds 403 and the skip check below would silently + # fall through to a real publish attempt that errors with + # "already exists on crates.io index" when re-running after a + # partial publish. Send a UA naming the workflow. local current - current=$(curl -fsSL "https://crates.io/api/v1/crates/${crate}" \ + current=$(curl -fsSL \ + -A 'ModernRelay-omnigraph-ci (https://github.com/ModernRelay/omnigraph)' \ + "https://crates.io/api/v1/crates/${crate}" \ | jq -r '.crate.max_version' || echo "") if [[ "$current" == "$version" ]]; then @@ -90,7 +97,20 @@ jobs: fi echo "==> publishing ${crate} ${version} (current crates.io: ${current:-none})" - cargo publish -p "$crate" --locked + # Defense in depth: if the skip check missed an existing + # version (e.g. crates.io API hiccup), cargo publish errors + # with "already exists on crates.io index". Treat that as + # success so the workflow can be re-run idempotently. + local output + if ! output=$(cargo publish -p "$crate" --locked 2>&1); then + echo "$output" + if echo "$output" | grep -q "already exists on crates.io"; then + echo "==> ${crate} ${version} was already published; treating as success" + return 0 + fi + return 1 + fi + echo "$output" } # Order matters: each crate must precede anything that depends on it.