Repository navigation
Refuse to create a mirror from scratch when it already exists elsewhere - #124
Conversation
`ensure_installed()` runs `datalad create` whenever the target directory is not an installed dataset. For a Dandiset whose submodule is registered in the superdataset but not installed -- a fresh clone of the superdataset, or a mirror directory removed by hand -- that starts a second, unrelated history (new datalad-id, one commit) under the existing name, and the next push collides with the real repository on GitHub (or, forced, overwrites it). The same holds for Zarr mirrors missing from `zarr_root`. `ensure_installed()` now takes a `before_create` hook, awaited only when the dataset is about to be created, and raising from it vetoes the creation: - Dandisets refuse (`MirrorMissingError`) if `.gitmodules` of the superdataset registers the ID, or if the GitHub repository exists. - Zarrs refuse if the Dandiset already has the Zarr as a submodule at the asset path, or if the `zarr_gh_org` repository exists. The GitHub check uses a new `GitHub.repo_exists()`, for which a 404 is an answer rather than an error, so it is neither retried nor logged as a failure. An installed mirror never reaches either check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
- Keep per-Dandiset state in .gitmodules (extending #69) instead of a separate index, including Zarr counts; drop the github-description cache in favour of comparing against GitHub's own. - Document why the per-Dandiset /versions/ call can go: superdataset history shows publish and unembargo always come with a draft 'modified' bump. - Add workflow_dispatch inputs mirroring the CLI, a per-run cap with size-based ordering, and a design for long-running backups (graceful deadline, routing to self-hosted runners, asynchronous recording), drawing on a review of dandi-compute. - Private repository for embargoed Dandisets; clarify self-hosted runner roles; note the H1/H2 guard is in #124. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
GitHub answers 404 also for a private repository the caller may not see, so repo_exists() now refuses to answer without a token, and on a 404 from a classic token (the only kind reporting X-OAuth-Scopes) requires the 'repo' scope. A bad token gets 401, which already fails via get_repo(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
|
CI note: the All 17 errors are Docker-backed tests. Everything else passes, including the new There's no fix yet: dandi-cli's Generated by Claude Code |
…leased MinIO made quay.io/minio/minio require a login on 2026-09-24, so pulling dandi-cli's test docker-compose stack fails with "unauthorized" and every Docker-backed test errors in setup. The fix belongs in dandi-cli (dandi/dandi-cli#1927) but reaches us only with a release, so meanwhile layer a compose override over upstream's file via COMPOSE_FILE: same image dandi-archive's CI uses, `command`/`tty` reset. It is a no-op once the upstream file carries the same change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
process_zarr() relied on nothing awaiting between "is this Zarr already being synced?" and recording that it is. The new submodule check awaited in between, so a second asset of the same Zarr (test_backup_zarr_pathological: sample.zarr and link.zarr) started a second sync_zarr() racing the first to `datalad create` the same directory. Record the Zarr first. Also give test_sync_zarr_converges_github_state's fake GitHub the async repo_exists() the creation guard now consults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #124 +/- ##
==========================================
+ Coverage 76.95% 77.39% +0.43%
==========================================
Files 19 19
Lines 3328 3375 +47
Branches 505 517 +12
==========================================
+ Hits 2561 2612 +51
+ Misses 571 565 -6
- Partials 196 198 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
…ix is released" This reverts commit 7b39464.
`Manager.gh` is created whenever `gh_org` is set, and the config requires the Dandiset and Zarr orgs to be set together, so a missing client there is a bug rather than a reason to skip the check. Also require dandi >= 0.80.1 for the tests: it pulls MinIO from bitnamilegacy/minio (dandi/dandi-cli#1927), replacing the override just reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK
AsyncDataset.ensure_installed()runsdatalad createwhenever the target directory isn't an installed dataset. So a Dandiset registered in the superdataset but not installed gets re-created with a new datalad-id and a one-commit history. That happens to an uninstalled submodule in a fresh clone ofdandi/dandisets, and to a mirror directory removed by hand on drogon. The next push then collides with the real repository on GitHub, or overwrites it if forced. Zarr mirrors missing fromzarr_roothave the same problem.This is hazard H1/H2 from the CI-mirroring plan (
docs/ci-mirroring-plan.mdonclaude/jolly-meitner-v8jo47), split out as its own PR. It's a prerequisite for running the update from a partial checkout, and it also protects drogon today.Changes
ensure_installed()takes a newbefore_createhook. It is awaited only when the dataset is about to be created, and raising from it cancels the creation. An installed mirror never reaches it.Dandisets: raise
MirrorMissingErrorif the superdataset's.gitmodulesregisters the ID, or ifgh_org/<id>exists on GitHub.Zarrs: raise if the Dandiset already has a submodule for that Zarr at the asset path, or if
zarr_gh_org/<zarr_id>exists. This also coversbackup-zarrs, which syncs into--partial-dir.New
GitHub.repo_exists(). A 404 is an answer here (not retried, not logged as an error), but GitHub also returns 404 for a private repo the caller can't see, so a 404 is only trusted when:X-OAuth-Scopesincluderepo, which is needed to see private repos (otherwise it raises).A bad token gets 401, which fails through
get_repo()'s usual path. Fine-grained and App tokens don't report scopes; they are the same token we push those repos with, so they can see them.The GitHub checks cost one
GETper brand-new Dandiset or Zarr.Testing
test/test_mirror_guard.py(markedai_generated), which needs no Docker. It checks that:repo_exists()returns the right answer for 200 and 404, and refuses to answer without a token or with a classic token lackingrepo.datalad create.flake8 src test,mypy src test, the new tests,test_zarrbargo.py,test_dirty_detection.py,test_util.py,test_aioutil.py,test_cfg_dandiset.py,test_quiescence.py.test_core.py/test_zarr.py. CI will cover those.🤖 Generated with Claude Code
https://claude.ai/code/session_01KoaLJfNiVVnwSDHNMg2bpK