Skip to content

fix(publish): discover before authenticating so an empty registry no-ops - #579

Merged
goosewobbler merged 1 commit into
mainfrom
fix/publish-authcheck-after-discover
Jul 19, 2026
Merged

goosewobbler merged 1 commit into
mainfrom
fix/publish-authcheck-after-discover

Conversation

@goosewobbler

@goosewobbler goosewobbler commented Jul 19, 2026 •

Copy link
Copy Markdown
Owner

Closes #578.

Problem

The publish dispatcher ran registry.authCheck() before registry.discover() and the zero-targets check. Since #563 flipped publish.cargo.enabled to default true, every repo now runs the cargo stage — and cargo.authCheck throws on a missing CARGO_REGISTRY_TOKEN. So a repo with no crates fails the whole release on cargo auth, after npm has already published (roll-forward), leaving it published-but-untagged and posting a false publish-failure report. ReleaseKit hit this on its own 0.41.0 release.

Fix

dispatcher.ts — discover first; if there are no targets, no-op the stage (skip auth and dispose); only authCheck when there's something to publish:

if (!registry.isEnabled(ctx.config)) return;

const targets = await registry.discover(ctx);   // was after authCheck; session dropped (unused)
if (targets.length === 0) { debug('…nothing to publish'); return; }

const session = await registry.authCheck(ctx);   // only when it matters
await registry.prepare?.(ctx, session);
// …publish loop…

This restores the interface's already-documented intent — "an enabled-but-empty stage stays side-effect-free" — which the auth ordering had violated.

Scope / safety

  • cargo — the reported failure; fixed.
  • npm — same structural flaw (npm.authCheck also throws pre-discovery); a zero-npm-package repo would hit it. Pre-existing, rarer; fixed by the same reorder.
  • pub.dev — was never affected (pub.authCheck is a no-op; token setup lives in prepare, gated on targets). No behavior change.
  • A repo that does have crates but no token still fails authCheck (targets > 0) — the real misconfiguration is preserved.
  • Registry.discover no longer receives session (no implementation used it).

Tests

Added a dispatcher regression test: an enabled registry with zero targets never calls authCheck and resolves cleanly even when authCheck would throw. Full gate green (publish 282 tests; pnpm turbo run lint typecheck test 32/32).

Sequencing

This needs to be on main before the 0.41.0 standing-PR retry — the retry re-runs the publish, and without this fix it will fail on cargo auth again.

🤖 Generated with Claude Code

Greptile Summary

This PR fixes a regression introduced in #563 where cargo.enabled defaulted to true, causing every repo (even those with no crates) to fail on a missing CARGO_REGISTRY_TOKEN because authCheck ran before discover. The fix reorders the dispatcher so discover runs first and an enabled-but-empty stage no-ops cleanly without demanding credentials.

  • dispatcher.ts: Moves discover() ahead of authCheck(); exits early with a debug log when targets.length === 0, skipping auth and dispose (no session is created for the empty case).
  • types.ts: Drops the unused session parameter from the discover interface signature, reflecting that no implementation used it and it no longer makes sense pre-auth.
  • dispatcher.spec.ts: Adds a generic regression test (enabled + zero targets → authCheck never called) and a real-cargoRegistry smoke test that exercises the exact failure path with no CARGO_REGISTRY_TOKEN.

Confidence Score: 5/5

Safe to merge — the change is a targeted reorder of two calls in a single function, backed by regression tests covering the exact broken scenario.

Discover runs first, auth is only demanded when there is something to publish, and the finally block that calls dispose is correctly scoped after authCheck so an empty stage never creates a session to clean up. Tests cover the generic dispatcher contract and the specific cargo regression.

No files require special attention.

Important Files Changed

Filename Overview
packages/publish/src/registry/dispatcher.ts Reorders discover() before authCheck(); early-returns on zero targets without touching auth or dispose — correct lifecycle change with no session created for the empty case.
packages/publish/src/registry/types.ts Removes the unused session parameter from the discover interface signature and updates the JSDoc to document the new pre-auth ordering guarantee.
packages/publish/test/unit/registry/dispatcher.spec.ts Adds two well-targeted regression tests: a synthetic test confirming authCheck is never called on zero targets, and a real cargoRegistry smoke test that exercises the exact pre-fix failure (no CARGO_REGISTRY_TOKEN, no crates).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant D as dispatcher
    participant R as Registry

    D->>R: isEnabled(config)
    alt disabled
        R-->>D: false
        D-->>D: log disabled, return
    else enabled
        R-->>D: true
        D->>R: discover(ctx)
        R-->>D: targets[]
        alt "targets.length === 0"
            D-->>D: debug No targets, return (no auth, no dispose)
        else "targets.length > 0"
            D->>R: authCheck(ctx)
            R-->>D: session
            D->>R: prepare?(ctx, session)
            loop for each target
                D->>R: precheckSkip? / isPublished / publish
            end
            D->>R: dispose?(session)
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant D as dispatcher
    participant R as Registry

    D->>R: isEnabled(config)
    alt disabled
        R-->>D: false
        D-->>D: log disabled, return
    else enabled
        R-->>D: true
        D->>R: discover(ctx)
        R-->>D: targets[]
        alt "targets.length === 0"
            D-->>D: debug No targets, return (no auth, no dispose)
        else "targets.length > 0"
            D->>R: authCheck(ctx)
            R-->>D: session
            D->>R: prepare?(ctx, session)
            loop for each target
                D->>R: precheckSkip? / isPublished / publish
            end
            D->>R: dispose?(session)
        end
    end
Loading

Reviews (2): Last reviewed commit: "fix(publish): discover before authentica..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Previous release v0.41.0 is partially published (4/4 packages on the registry; no tags/GitHub release created).
Retry it via release:retry on the merged standing PR (#555), or merging this PR supersedes it — the next release re-publishes everything at the new version.

Release Preview — v0.40.0 → v0.41.1

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

Merging this PR will create a new release PR with the following changes:

Changelog

monorepo 0.40.0 → 0.41.1

Fixed

  • discover before authenticating so an empty registry no-ops (publish) (#578)

All packages → 0.41.1 (sync versioning)

  • @releasekit/version
  • @releasekit/notes
  • @releasekit/publish
  • @releasekit/release

Updated automatically by ReleaseKit

The dispatcher ran each registry's authCheck before discovering targets, so
an enabled stage with nothing to publish still demanded credentials — and a
crateless repo (cargo enabled by default since the ecosystem-enablement
change) failed the whole release with "CARGO_REGISTRY_TOKEN not set" after
npm had already published.

Discover first; if there are no targets, no-op the stage (skip auth and
dispose entirely); only authCheck when there is something to publish. This
matches the already-stated "enabled-but-empty stays side-effect-free"
intent, restores it for auth, and fixes cargo, npm, and pub uniformly. A
repo that does have crates but no token still fails authCheck as before.
`discover` no longer takes the (unused) session, since it now runs first.

Closes #578

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i1KvoyxATU3bz5V5Sf1Xu
@goosewobbler
goosewobbler force-pushed the fix/publish-authcheck-after-discover branch from 37cf59a to 5041be0 Compare July 19, 2026 20:52
@goosewobbler

Copy link
Copy Markdown
Owner Author

Strengthened the test coverage: alongside the mock-registry ordering test, added one that drives the real cargoRegistry — crateless input, cargo enabled by default, CARGO_REGISTRY_TOKEN unset, non-dry-run → asserts the stage no-ops. It exercises the real authCheck + crate discovery (which the mock test can't), so it's the test that would have actually caught the shipped bug.

@goosewobbler
goosewobbler merged commit b47ca09 into main Jul 19, 2026
24 checks passed
@goosewobbler
goosewobbler deleted the fix/publish-authcheck-after-discover branch July 19, 2026 20:55
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026
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.

Release fails on cargo auth for repos with no crates (regression from #563 default flip)

1 participant