Detect drift between GitHub releases and Zenodo archives
Project description
zenodo-release-drift
Detect drift between GitHub releases and Zenodo archives.
When a repository is connected to Zenodo via the GitHub–Zenodo webhook, every new release should be automatically archived.
In practice the webhook silently fails, gets disabled, or simply falls behind.
zenodo-release-drift surfaces those gaps so you can act on them.
Installation
pip install zenodo-release-drift
# or with uv
uv add zenodo-release-drift
Quick start
Check a single repository:
zenodo-release-drift check owner/repo
Scan every repository owned by a GitHub user or org:
zenodo-release-drift check example-org
Example output:
GitHub user or org: example-org
28 repos found, 5 with Zenodo integration.
Repository Code Description Details
-------------------------- ------ ----------------------- -------------------------------------------
example-org/example-repo ZRD001 Release(s) not archived 15 release(s) not archived:
1.2.0, 1.1.4, 1.0.1, ...
example-org/example-repo ZRD002 Zenodo out of date Zenodo latest: 1.1.3 | GitHub latest: 1.2.0
Exit code is 1 when drift is found (single-repo mode), 0 when clean — suitable for CI and pre-commit hooks.
Commands
check
zenodo-release-drift check [OPTIONS] TARGET
TARGET is either owner/repo (single repository) or a GitHub username/org
(scans all owned repositories).
| Option | Description |
|---|---|
--json |
Output findings as JSON |
--markdown |
Output as a Markdown table (single repo only) |
--explain |
Full human-readable explanation of each finding (single repo only) |
lint
Explicit single-repository check — useful in pre-commit hooks where the command name should be unambiguous.
zenodo-release-drift lint [OPTIONS] OWNER/REPO
Same options as check.
fix
Upload releases that are missing from Zenodo back to the archive.
zenodo-release-drift fix [OPTIONS] OWNER/REPO
Requires a ZENODO_TOKEN environment variable — a personal access token
created at https://zenodo.org/account/settings/applications/.
| Option | Description |
|---|---|
--version |
Upload only this specific version (default: all missing ones) |
--from |
Only upload versions at or above this semver (inclusive) |
--to |
Only upload versions at or below this semver (inclusive) |
--sandbox |
Target sandbox.zenodo.org instead of production |
--json |
Output results as JSON |
How versions are grouped: if Zenodo already holds records for the repository, each upload is created as a new version under the same concept DOI so all versions remain linked. If no existing records are found, a new concept is created.
Note on uploaded source content: the archive uploaded for each version is fetched from GitHub's tag archive endpoint at the moment fix runs, and reflects where the tag points at that time.
For the vast majority of repositories this is identical to the original release — tags are not normally moved after publishing.
If a tag has been amended since the original release was made, the archive will reflect the current state of the tag rather than its historical state; this is a property of how git tags work rather than a limitation of this tool.
Important — record ownership
The
ZENODO_TOKENyou supply must belong to the same Zenodo account that owns the existing records. When the original records were created by the GitHub–Zenodo webhook they are owned by whichever Zenodo account connected the webhook — not necessarily yours. If your token belongs to a different account thenewversionAPI call will return HTTP 403 and the upload will fail with a clear hint message.To resolve a 403:
- Log in to Zenodo as the record owner.
- Open the existing record and go to Edit → Share.
- Grant your account the Curator role, or ask the Zenodo support team to transfer ownership.
- Re-run
fixonce access is granted.
# Upload every missing release
export ZENODO_TOKEN=your-token-here
zenodo-release-drift fix owner/repo
# Upload a single specific release
zenodo-release-drift fix owner/repo --version 1.2.3
# Upload all missing releases from 1.0.0 onwards
zenodo-release-drift fix owner/repo --from 1.0.0
# Upload all missing releases up to and including 1.4.0
zenodo-release-drift fix owner/repo --to 1.4.0
# Upload missing releases within a range
zenodo-release-drift fix owner/repo --from 1.0.0 --to 1.4.0
# Test against the Zenodo sandbox before touching production
export ZENODO_TOKEN=your-sandbox-token-here
zenodo-release-drift fix owner/repo --sandbox
version
zenodo-release-drift version
Check codes
| Code | Description |
|---|---|
| ZRD001 | A GitHub release exists with no matching Zenodo archive |
| ZRD002 | The latest Zenodo version is behind the latest GitHub release |
Authentication
GitHub (GITHUB_TOKEN)
By default the tool makes unauthenticated GitHub API calls (60 requests/hour limit).
Set GITHUB_TOKEN to raise this to 5,000 requests/hour:
export GITHUB_TOKEN=ghp_...
zenodo-release-drift check my-org
Zenodo (ZENODO_TOKEN)
The fix command requires a Zenodo personal access token with the
deposit:write scope.
- Log in to https://zenodo.org (or https://sandbox.zenodo.org for testing).
- Go to Account → Applications → Personal access tokens.
- Create a token with the
deposit:writescope. - Export it before running
fix:
export ZENODO_TOKEN=your-token-here
zenodo-release-drift fix owner/repo
Pre-commit hook
Add to .pre-commit-config.yaml to gate commits on a single repository.
Set args to the repository you want to check:
- repo: local
hooks:
- id: zenodo-release-drift
name: Zenodo release drift
entry: zenodo-release-drift lint
args: ["owner/repo"]
language: python
pass_filenames: false
always_run: true
Python API
from zenodo_release_drift import lint_repo, lint_repo_explain
# Returns a list of finding dicts
findings = lint_repo("owner", "repo")
# Returns a Markdown string with explanations
report = lint_repo_explain("owner", "repo")
Development
git clone https://github.com/d33bs/zenodo-release-drift
cd zenodo-release-drift
uv sync --all-groups
uv run poe pipeline # pre-commit + tests
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zenodo_release_drift-0.0.2.tar.gz.
File metadata
- Download URL: zenodo_release_drift-0.0.2.tar.gz
- Upload date:
- Size: 119.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d66ad734ac35aac1fa854a8ff130d58a8987149df457b34de782f1fef6fd8ed2
|
|
| MD5 |
e062eac51cc3020d599f7164825778db
|
|
| BLAKE2b-256 |
c89788ae9c67cab8a5cd7a4b0e7ee5f9eaef09621b4f80fba8777512cc1916e9
|
Provenance
The following attestation bundles were made for zenodo_release_drift-0.0.2.tar.gz:
Publisher:
publish-pypi.yml on CU-DBMI/zenodo-release-drift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenodo_release_drift-0.0.2.tar.gz -
Subject digest:
d66ad734ac35aac1fa854a8ff130d58a8987149df457b34de782f1fef6fd8ed2 - Sigstore transparency entry: 1791446230
- Sigstore integration time:
-
Permalink:
CU-DBMI/zenodo-release-drift@a66e631abcaa9650cacc69d24ea59fa924e5a711 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/CU-DBMI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@a66e631abcaa9650cacc69d24ea59fa924e5a711 -
Trigger Event:
release
-
Statement type:
File details
Details for the file zenodo_release_drift-0.0.2-py3-none-any.whl.
File metadata
- Download URL: zenodo_release_drift-0.0.2-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28ba9332f15dd8265b9e67847b786be4c5aa6d62b94807f9b2455ea1d4abfb5d
|
|
| MD5 |
c8464e4f02b9a22b10f008d47bbfbdaa
|
|
| BLAKE2b-256 |
727cab859873b35762c5271f78ba1f630501d55bac9e594b81a1a3a6968b343b
|
Provenance
The following attestation bundles were made for zenodo_release_drift-0.0.2-py3-none-any.whl:
Publisher:
publish-pypi.yml on CU-DBMI/zenodo-release-drift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zenodo_release_drift-0.0.2-py3-none-any.whl -
Subject digest:
28ba9332f15dd8265b9e67847b786be4c5aa6d62b94807f9b2455ea1d4abfb5d - Sigstore transparency entry: 1791446576
- Sigstore integration time:
-
Permalink:
CU-DBMI/zenodo-release-drift@a66e631abcaa9650cacc69d24ea59fa924e5a711 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/CU-DBMI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@a66e631abcaa9650cacc69d24ea59fa924e5a711 -
Trigger Event:
release
-
Statement type: