Skip to content

ci: auto-publish PyPI + npm on v*.*.* tags#60

Closed
Gradata wants to merge 1 commit into
mainfrom
ci/auto-publish-on-tag
Closed

ci: auto-publish PyPI + npm on v*.*.* tags#60
Gradata wants to merge 1 commit into
mainfrom
ci/auto-publish-on-tag

Conversation

@Gradata

@Gradata Gradata commented Apr 15, 2026

Copy link
Copy Markdown
Owner

GitHub Actions workflows that auto-publish Python SDK to PyPI and npm wrapper to npm on version tag push. Requires PYPI_API_TOKEN + NPM_TOKEN secrets — setup instructions in docs/RELEASING.md.

What ships

  • .github/workflows/publish-python.yml — fires on v*.*.* tags, builds sdist+wheel via python -m build, uploads via twine using PYPI_API_TOKEN. Refuses to publish if tag version != pyproject.toml version.
  • .github/workflows/publish-npm.yml — fires on v*.*.* tags, matrix publishes gradata-install (unscoped) and @gradata/install (scoped) from gradata-install/ using NPM_TOKEN. Same version-match guard vs package.json.
  • docs/RELEASING.md — release cut procedure, required secrets, token rotation.

Notes

  • Task spec mentioned npm ci in gradata-install/ — dropped in simplify pass because the package has no dependencies and no lockfile (npm ci would fail). Publishing only needs the files listed in package.json's files array.
  • Workflows use pinned action versions (@v4, @v5), no secrets in YAML (only ${{ secrets.X }} references), and are tag-push-only so this PR merge will not trigger a publish.
  • Existing sdk-release.yml (Trusted Publisher on v*) will also fire on the same tag. Both are idempotent on already-published versions, but before the first real release we should decide whether to delete sdk-release.yml or keep these as the canonical path.

Test plan

  • Merge PR — no workflow fires (tag-push only)
  • Add PYPI_API_TOKEN and NPM_TOKEN at Settings → Secrets → Actions
  • Bump pyproject.toml + gradata-install/package.json to same version
  • git tag vX.Y.Z && git push origin vX.Y.Z → verify both workflows go green and packages appear on PyPI / npm

Signed-off-by: Oliver Le <oliver@gradata.ai>

@greptile-apps greptile-apps 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.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
📝 Walkthrough
  • Adds GitHub Actions workflow (publish-python.yml) to automatically publish the Python SDK to PyPI when git tags matching v*.*.* are pushed
  • Adds GitHub Actions workflow (publish-npm.yml) to automatically publish npm packages (gradata-install and @gradata/install) to npm registry on matching version tags
  • Implements version validation in both workflows to ensure tag version matches pyproject.toml (Python) and package.json (npm) before publishing
  • Adds docs/RELEASING.md documenting the automated release procedure, including step-by-step instructions, required GitHub Actions secrets (PYPI_API_TOKEN, NPM_TOKEN), and token rotation guidance
  • Both workflows use concurrency groups to prevent cancellation of in-progress publishes and include idempotent publish steps for already-released versions
  • Workflows use pinned action versions and only reference secrets via ${{ secrets.X }}; tag-only triggers ensure merging this PR does not immediately trigger a publish
  • Notes that existing sdk-release.yml (Trusted Publisher) also triggers on the same tags and maintainers should decide whether to keep or remove it before the first release

Walkthrough

Adds two new GitHub Actions workflows for automated publishing to npm and PyPI repositories, triggered by semantic version tags. Includes accompanying release documentation detailing the tagging, version verification, and publishing process.

Changes

Cohort / File(s) Summary
GitHub Actions Publishing Workflows
.github/workflows/publish-npm.yml, .github/workflows/publish-python.yml
Two parallel publishing workflows: npm workflow publishes gradata-install and @gradata/install packages; Python workflow publishes gradata to PyPI. Both workflows validate tag version matches manifest versions, build artifacts, and publish to respective registries.
Release Documentation
docs/RELEASING.md
Documentation describing the automated release process, step-by-step instructions for bumping versions and tagging releases, failure recovery procedures, and GitHub Actions secret management guidelines.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

feature, docs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding CI workflows to auto-publish packages on version tags.
Description check ✅ Passed The description is directly related to the changeset, detailing the three added files, their purposes, implementation notes, and test plan.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/auto-publish-on-tag

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

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 34264a0
Status: ✅  Deploy successful!
Preview URL: https://fe7826af.gradata-dashboard.pages.dev
Branch Preview URL: https://ci-auto-publish-on-tag.gradata-dashboard.pages.dev

View logs

@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: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/publish-npm.yml:
- Around line 26-33: Replace the mutable action tags with pinned commit SHAs:
update the uses fields for actions/checkout and actions/setup-node (the lines
referencing "uses: actions/checkout@v4" and "uses: actions/setup-node@v4") to
the corresponding full commit SHAs for the desired release, and optionally add a
trailing comment with the readable tag (e.g., "# v4.1.1") for clarity; ensure
both entries use immutable SHAs and verify the SHAs match the intended release
before committing.
- Around line 15-18: Add an explicit least-privilege permissions block to the
publish job so it doesn't get a broad default token: in the GitHub Actions job
named "publish" (the jobs.publish block driving matrix.pkg_name) add a
permissions mapping that restricts the workflow token to contents: read only,
ensuring the job still can checkout and verify packages while preventing
unnecessary repo write or admin scopes.
- Around line 50-54: The "Publish to npm" step currently runs npm publish
directly (env NODE_AUTH_TOKEN) which will fail if the same package version is
already published during reruns or concurrent workflows; modify the step to
first check whether the package@version exists (e.g., use npm view
<pkg>@<version> or read package.json version into a variable and query npm) and
only run npm publish --access public when the version is not found, otherwise
skip/passthrough the publish; update the "Publish to npm" step to perform this
pre-check and short-circuit the publish to prevent "version already exists"
failures.

In @.github/workflows/publish-python.yml:
- Around line 18-23: Replace the mutable major-version action references with
immutable full commit SHAs: change actions/checkout@v4 and
actions/setup-python@v5 to their corresponding full SHA pins (e.g.,
actions/checkout@<full-sha> and actions/setup-python@<full-sha>) and add an
inline comment with the original tag (e.g., // actions/checkout@v4) for
readability and Dependabot compatibility; ensure you use the canonical commit
SHAs from the official GitHub Actions repos when updating the workflow.
- Around line 14-17: The publish job currently lacks explicit permissions; add a
permissions block to the publish job (job name "publish") granting only the
least privilege needed, e.g. set contents: read so GITHUB_TOKEN won't inherit
broader repo defaults; update the job definition that uses actions/checkout@v4
to include permissions: { contents: read } directly under the publish job
header.
- Line 46: Update the twine upload invocation to be idempotent by adding the
--skip-existing flag to the existing command `python -m twine upload
--non-interactive dist/*` so it becomes `python -m twine upload
--non-interactive --skip-existing dist/*`; this ensures reruns won't fail when
artifacts are already published.

In `@docs/RELEASING.md`:
- Around line 50-52: Update the token-rotation verification instruction that
currently suggests pushing a throwaway tag like `v0.0.0-rc.verify` because CI
requires tag ↔ manifest version parity; instead instruct the user to either
re-run an existing release workflow for the exact release tag already present in
the manifest or create a temporary test tag that matches the manifest version
(e.g., use the actual release version string rather than `v0.0.0-rc.verify`),
then delete the test tag after verifying the token; update the sentence
referencing `v0.0.0-rc.verify` to reflect this exact-match requirement so
readers know to use a tag that matches the release manifest or re-run the prior
workflow.
- Around line 20-24: Add a blank line immediately before the opening fenced code
block and a blank line immediately after the closing ``` fence around the block
that contains "git checkout main && git pull", "git tag vX.Y.Z", and "git push
origin vX.Y.Z" so the fenced-block satisfies markdownlint MD031; edit the fenced
block in RELEASING.md (the ```bash ... ``` section) to insert one empty line
before the opening ```bash and one empty line after the closing ``` to restore
proper spacing.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 3e9ed6fd-4599-400c-92dc-b66000e2236e

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd7215 and 34264a0.

📒 Files selected for processing (3)
  • .github/workflows/publish-npm.yml
  • .github/workflows/publish-python.yml
  • docs/RELEASING.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.0)
docs/RELEASING.md

[warning] 20-20: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 24-24: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

Comment on lines +15 to +18
jobs:
publish:
name: Publish ${{ matrix.pkg_name }}
runs-on: ubuntu-latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/publish-npm.yml

Repository: Gradata/gradata

Length of output: 2160


Add explicit least-privilege permissions for this job.

The publish job currently lacks explicit permissions, which means it receives a token with default (broader) permissions. Since the job only reads repository contents (checkout, package verification) and uses a separate NPM_TOKEN secret for authentication to npm, restrict the token scope to contents: read:

🔒 Proposed fix
 jobs:
   publish:
     name: Publish ${{ matrix.pkg_name }}
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     strategy:
📝 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
jobs:
publish:
name: Publish ${{ matrix.pkg_name }}
runs-on: ubuntu-latest
jobs:
publish:
name: Publish ${{ matrix.pkg_name }}
runs-on: ubuntu-latest
permissions:
contents: read
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-npm.yml around lines 15 - 18, Add an explicit
least-privilege permissions block to the publish job so it doesn't get a broad
default token: in the GitHub Actions job named "publish" (the jobs.publish block
driving matrix.pkg_name) add a permissions mapping that restricts the workflow
token to contents: read only, ensuring the job still can checkout and verify
packages while preventing unnecessary repo write or admin scopes.

Comment on lines +26 to +33
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/publish-npm.yml | sed -n '26,33p'

Repository: Gradata/gradata

Length of output: 330


🌐 Web query:

GitHub Actions best practices pinning versions immutable SHA vs major version tags

💡 Result:

In GitHub Actions, the best practice for pinning versions prioritizes security by using full-length commit SHAs for immutability, while major version tags offer a balance of convenience and relative stability for trusted actions. SHA pinning (e.g., uses: actions/checkout@11bd719 # v4.2.2) provides the highest security: SHAs are cryptographically immutable, preventing supply chain attacks where tags are moved to malicious code. GitHub's official Security Hardening guide explicitly states: "Pinning an action to a full-length commit SHA is currently the only way to use an action as an immutable release" and helps mitigate backdoors requiring SHA-1 collisions. This is recommended for all third-party actions, especially high-risk ones handling secrets. Major version tags (e.g., @v4) are mutable but safer than branches like main or latest. Use them only for trusted creators (e.g., GitHub's 'actions/*' with Verified creator badge). Tags allow minor/patch updates for fixes but risk tag movement if compromised. Avoid: @main, @latest, short SHAs. Additional practices: - Add version comments (e.g., # v4.1.1) for readability; Dependabot recognizes them for updates. - Use GitHub policies to enforce SHA pinning at repo/org level (since Aug 2025). - Audit action source code. - Enable Dependabot for SHA-pinned action updates and vulnerability alerts. - Pin Docker images to digests (@sha256:...). Example workflow step: uses: actions/checkout@b4ffde6 # v4.1.1 To find SHA: gh api repos/actions/checkout/git/refs/tags/v4.1.1 --jq .object.sha or check Releases page commit link.

Citations:


Pin GitHub Actions to immutable commit SHAs.

Lines 27 and 30 use mutable major tags (@v4). Switch to full commit SHAs for supply-chain security in this publish workflow. Example: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-npm.yml around lines 26 - 33, Replace the mutable
action tags with pinned commit SHAs: update the uses fields for actions/checkout
and actions/setup-node (the lines referencing "uses: actions/checkout@v4" and
"uses: actions/setup-node@v4") to the corresponding full commit SHAs for the
desired release, and optionally add a trailing comment with the readable tag
(e.g., "# v4.1.1") for clarity; ensure both entries use immutable SHAs and
verify the SHAs match the intended release before committing.

Comment on lines +50 to +54
- name: Publish to npm
working-directory: gradata-install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat .github/workflows/publish-npm.yml

Repository: Gradata/gradata

Length of output: 1782


Guard npm publish to prevent failures on workflow reruns or concurrent executions.

The workflow uses cancel-in-progress: false in its concurrency configuration, which allows multiple runs to execute concurrently or enables workflow reruns. When this occurs, both the initial run and rerun will attempt to publish the same version. Since npm publish fails with "version already exists" when a version is already published, this causes CI/CD failures.

♻️ Proposed fix
       - name: Publish to npm
         working-directory: gradata-install
         env:
           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
-        run: npm publish --access public
+        run: |
+          PKG_NAME=$(node -p "require('./package.json').name")
+          PKG_VERSION=$(node -p "require('./package.json').version")
+          if npm view "${PKG_NAME}@${PKG_VERSION}" version >/dev/null 2>&1; then
+            echo "Already published: ${PKG_NAME}@${PKG_VERSION}; skipping."
+            exit 0
+          fi
+          npm publish --access public
📝 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
- name: Publish to npm
working-directory: gradata-install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public
- name: Publish to npm
working-directory: gradata-install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
if npm view "${PKG_NAME}@${PKG_VERSION}" version >/dev/null 2>&1; then
echo "Already published: ${PKG_NAME}@${PKG_VERSION}; skipping."
exit 0
fi
npm publish --access public
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-npm.yml around lines 50 - 54, The "Publish to npm"
step currently runs npm publish directly (env NODE_AUTH_TOKEN) which will fail
if the same package version is already published during reruns or concurrent
workflows; modify the step to first check whether the package@version exists
(e.g., use npm view <pkg>@<version> or read package.json version into a variable
and query npm) and only run npm publish --access public when the version is not
found, otherwise skip/passthrough the publish; update the "Publish to npm" step
to perform this pre-check and short-circuit the publish to prevent "version
already exists" failures.

Comment on lines +14 to +17
publish:
name: Build and publish gradata to PyPI
runs-on: ubuntu-latest
steps:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read its content
cat -n .github/workflows/publish-python.yml

Repository: Gradata/gradata

Length of output: 1700


Add explicit least-privilege token permissions.

The publish job lacks a permissions block, causing GITHUB_TOKEN to inherit repository defaults which may grant broader access than necessary. Since the job uses actions/checkout@v4 and reads repository files, add explicit contents: read permission.

🔒 Proposed fix
 jobs:
   publish:
     name: Build and publish gradata to PyPI
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
📝 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
publish:
name: Build and publish gradata to PyPI
runs-on: ubuntu-latest
steps:
publish:
name: Build and publish gradata to PyPI
runs-on: ubuntu-latest
permissions:
contents: read
steps:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-python.yml around lines 14 - 17, The publish job
currently lacks explicit permissions; add a permissions block to the publish job
(job name "publish") granting only the least privilege needed, e.g. set
contents: read so GITHUB_TOKEN won't inherit broader repo defaults; update the
job definition that uses actions/checkout@v4 to include permissions: { contents:
read } directly under the publish job header.

Comment on lines +18 to +23
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub Actions pinning versions commit SHA best practices security

💡 Result:

Best practices for pinning GitHub Actions versions using commit SHAs focus on enhancing security by ensuring immutability and preventing supply chain attacks. Official GitHub documentation and recent updates (as of 2025) emphasize pinning to full-length (40-character) commit SHAs as the only way to use actions as immutable releases. Key recommendations: - Always pin actions to full commit SHAs, e.g., uses: actions/checkout@11bd719 # v4.2.2. Include a comment with the corresponding version tag for readability and to enable automated updates via Dependabot. - Avoid using @vX tags or @main for third-party actions unless you fully trust the maintainer, as tags are mutable and can be updated or compromised. - Verify SHAs come from the official repository, not forks. - For first-party GitHub actions (e.g., actions/checkout), tags from verified creators may be acceptable with caution, but SHAs are preferred. - Use GitHub's organization or repository policies to enforce SHA pinning: workflows using unpinned actions will fail. - Automate pinning and updates: Tools like Dependabot, Renovate, Piñata (caarlos0/pinata), or StepSecurity can pin actions and create PRs for updates. - Audit action source code, especially for third-party ones handling secrets. - Regularly update pinned SHAs for security fixes, balancing security with maintenance. Example workflow snippet: - uses: actions/checkout@11bd719 # v4.2.2 - uses: actions/setup-node@1e60f62 # v4.0.3 This practice mitigates risks like malicious code injection via tag overwrites, as seen in real incidents.

Citations:


Pin actions to full commit SHAs instead of mutable major version tags.

Lines 19 and 22 use major version tags (@v4, @v5), which are mutable and can be compromised. Pin to full commit SHAs (e.g., actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683) to ensure immutability and reduce supply-chain risk. Include a comment with the version tag for readability and to enable automated updates via Dependabot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-python.yml around lines 18 - 23, Replace the
mutable major-version action references with immutable full commit SHAs: change
actions/checkout@v4 and actions/setup-python@v5 to their corresponding full SHA
pins (e.g., actions/checkout@<full-sha> and actions/setup-python@<full-sha>) and
add an inline comment with the original tag (e.g., // actions/checkout@v4) for
readability and Dependabot compatibility; ensure you use the canonical commit
SHAs from the official GitHub Actions repos when updating the workflow.

env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: python -m twine upload --non-interactive dist/*

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the workflow file
find . -name "publish-python.yml" -type f

Repository: Gradata/gradata

Length of output: 97


🏁 Script executed:

# If found, read the file to check line 46
if [ -f ".github/workflows/publish-python.yml" ]; then
  wc -l ".github/workflows/publish-python.yml"
  cat -n ".github/workflows/publish-python.yml"
fi

Repository: Gradata/gradata

Length of output: 1740


🌐 Web query:

twine upload --skip-existing flag behavior PyPI

💡 Result:

The --skip-existing flag in twine upload allows the command to continue uploading other distribution files even if one already exists on PyPI (or TestPyPI), instead of failing with an error. PyPI rejects duplicate uploads (typically with a 409 status code), so twine attempts the upload, receives the error response, checks if it indicates the file already exists (via status code and response content), prints a WARNING like "Skipping [filename] because it appears to already exist", and proceeds with remaining files without exiting non-zero. Key behaviors: - Shows an "Uploading" progress bar for every file (including skipped ones), as existence can only be reliably confirmed by attempting upload (no pre-check for all repositories). - Only supported for PyPI and TestPyPI; in twine 6.2.0 (released 2025-09-04), hacks for other indexes were removed. - Exit code is 0 if all uploads succeed or are safely skipped; non-zero only for unskippable errors. - Added in early versions (e.g., twine 1.6.0), with fixes over time (e.g., for 409 codes, first-time uploads). As of 2026-04-15 (post-6.2.0), the flag remains available in docs/help for PyPI use, despite some non-authoritative claims of deprecation.

Citations:


🌐 Web query:

twine upload already published version error handling

💡 Result:

When using twine to upload a Python package to PyPI and encountering the "already published version" or "file already exists" error (HTTP 400), PyPI prevents overwriting distribution files for the same project version to ensure immutability. Primary handling options: 1. Use twine upload --skip-existing dist/: Skips files that already exist on PyPI, continues with others. Supported for PyPI/TestPyPI in twine 6.2.0 (2025-09-04). Note: Hacks for other repositories removed in 6.2.0. 2. Bump the package version: Update version in pyproject.toml/setup.py (e.g., 1.0.0 -> 1.0.1 or 1.0.0.post1), rebuild (python -m build), and re-upload. Recommended for actual changes. 3. Delete and re-upload (limited): Project owners can delete releases via PyPI web interface, but filenames remain reserved forever. Not recommended for production. Workflow: rm -rf dist/ build/ python -m build twine upload --skip-existing dist/ Or for new version: # Update version, then: python -m build twine upload dist/* Always run twine check dist/* first. As of 2026-04-15, use twine 6.2.0+ for latest features.

Citations:


Add --skip-existing flag to make PyPI publish step idempotent for reruns.

Line 46 uses twine upload without --skip-existing. GitHub Actions allows manual reruns of workflows, and without this flag, attempting to republish an already-published version will fail with a non-zero exit code, preventing any ability to retry on transient failures.

♻️ Proposed fix
-        run: python -m twine upload --non-interactive dist/*
+        run: python -m twine upload --non-interactive --skip-existing dist/*
📝 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
run: python -m twine upload --non-interactive dist/*
run: python -m twine upload --non-interactive --skip-existing dist/*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-python.yml at line 46, Update the twine upload
invocation to be idempotent by adding the --skip-existing flag to the existing
command `python -m twine upload --non-interactive dist/*` so it becomes `python
-m twine upload --non-interactive --skip-existing dist/*`; this ensures reruns
won't fail when artifacts are already published.

Comment thread docs/RELEASING.md
Comment on lines +20 to +24
```bash
git checkout main && git pull
git tag vX.Y.Z
git push origin vX.Y.Z
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix fenced-code spacing to satisfy markdownlint MD031.

Add blank lines before and after the fenced block around Lines 20–24.

📝 Proposed fix
 3. Tag and push:
+
    ```bash
    git checkout main && git pull
    git tag vX.Y.Z
    git push origin vX.Y.Z
    ```
+
 4. Watch the workflows at **Actions** → *Publish Python to PyPI* and
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 20-20: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 24-24: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/RELEASING.md` around lines 20 - 24, Add a blank line immediately before
the opening fenced code block and a blank line immediately after the closing ```
fence around the block that contains "git checkout main && git pull", "git tag
vX.Y.Z", and "git push origin vX.Y.Z" so the fenced-block satisfies markdownlint
MD031; edit the fenced block in RELEASING.md (the ```bash ... ``` section) to
insert one empty line before the opening ```bash and one empty line after the
closing ``` to restore proper spacing.

Comment thread docs/RELEASING.md
Comment on lines +50 to +52
4. Push a throwaway pre-release tag (e.g. `v0.0.0-rc.verify`) on a fork or
re-run a prior release workflow to confirm the new token works, then
delete the test tag.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Token-rotation verification step is not executable as written.

Lines 50–52 suggest using v0.0.0-rc.verify, but workflows require exact tag↔manifest version matches, so this usually fails before token validation.

📝 Proposed doc correction
-4. Push a throwaway pre-release tag (e.g. `v0.0.0-rc.verify`) on a fork or
-   re-run a prior release workflow to confirm the new token works, then
-   delete the test tag.
+4. Re-run a prior release workflow to validate authentication behavior, or
+   validate on a repository/fork where manifests are intentionally set to the
+   exact test tag version. (With this repo’s guards, tag/version mismatch fails
+   before token use.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/RELEASING.md` around lines 50 - 52, Update the token-rotation
verification instruction that currently suggests pushing a throwaway tag like
`v0.0.0-rc.verify` because CI requires tag ↔ manifest version parity; instead
instruct the user to either re-run an existing release workflow for the exact
release tag already present in the manifest or create a temporary test tag that
matches the manifest version (e.g., use the actual release version string rather
than `v0.0.0-rc.verify`), then delete the test tag after verifying the token;
update the sentence referencing `v0.0.0-rc.verify` to reflect this exact-match
requirement so readers know to use a tag that matches the release manifest or
re-run the prior workflow.

@Gradata Gradata closed this Apr 15, 2026
@Gradata Gradata deleted the ci/auto-publish-on-tag branch April 15, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant