Explain suspended mirrors in entire repo mirror create#1344
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Improves the UX of entire repo mirror create when the clone-readiness probe fails due to STS token exchange returning invalid_target (commonly caused by suspended mirror placements), by classifying the auth failure via a sentinel error and rendering an actionable recovery message instead of the raw OAuth text.
Changes:
auth: IntroducesErrRepoTargetUnknownand maps STSinvalid_targettoken-exchange failures to it (while preserving the original STS error text in the error chain).repo mirror create: AddsexplainSuspendedMirrorhandling so this specific auth failure is rendered as a clear “registered but not servable” message plus the operator resume command, returningSilentErrorto avoid duplicate/raw error output.- Tests: Adds coverage for invalid-target wrapping and the suspended-mirror explanation behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/repo_mirror.go | Hooks suspended-mirror explanation into the create clone-probe failure path. |
| cmd/entire/cli/repo_mirror_test.go | Adds unit tests asserting the explanation + SilentError behavior. |
| cmd/entire/cli/repo_mirror_probe.go | Implements explainSuspendedMirror to translate ErrRepoTargetUnknown into an actionable message. |
| cmd/entire/cli/auth/repo_token.go | Adds ErrRepoTargetUnknown and detects STS invalid_target to wrap with the sentinel. |
| cmd/entire/cli/auth/repo_token_test.go | Adds test exercising the STS error-decoding path and verifying sentinel wrapping + preserved detail. |
cba5e28 to
5d735a7
Compare
When `entire repo mirror create` hits a suspended placement, create reports "Mirror already exists" (it's idempotent on (repo, cluster) and ignores suspended_at) while the clone probe's token exchange fails with the opaque `invalid_target: no mirror at this URL` — the data plane's auth gate hides suspended mirrors behind invalid_target as an enumeration guard. Surface the actionable cause instead of the raw OAuth error: - auth: add the ErrRepoTargetUnknown sentinel and wrap STS invalid_target responses with it (preserving the verbatim code + description via a second %w). Detection matches the RFC 8693 code in the rendered string because auth-go's sts package flattens the OAuth error with no typed code. - repo mirror create: when the clone probe fails for a placement we just confirmed exists, print the likely cause (suspended after upstream access loss) and the exact `entire-core admin mirrors resume <id>` recovery command, then return a SilentError. Unrelated probe errors pass through verbatim. No server or behavior change — suspension stays operator-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the substring match on the rendered STS error with errors.As on the typed sts.ExchangeError + Code == "invalid_target", dropping the coupling to auth-go's message format. Pulls auth-go as a pseudo-version of the sts-typed-exchange-error branch (to be repointed at v0.5.0 once tagged). That build raises the stdlib floor, so bump Go to 1.26.4 to match — which also picks up the GO-2026-5037 / GO-2026-5039 standard-library fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fresh create that races into invalid_target is propagation lag, not suspension — a mirror created moments ago cannot be suspended. Gate the suspended-mirror diagnosis on a non-fresh create so we don't misdirect users to a `mirrors resume` command that does nothing; let the raw error surface instead. The precondition is enforced inside explainSuspendedMirror so it can't be missed by a future caller. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: e5f7e2236cbb
fe609ca to
928dd85
Compare
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 928dd85. Configure here.
pjbgf
approved these changes
Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/497
Summary
When
entire repo mirror createhits a suspended mirror placement, the output is baffling — it printsMirror already existsand then fails with the raw OAuth errorinvalid_target: no mirror at this URL. Those read as contradictory.They aren't. They come from two lookups that disagree about suspended rows:
createis idempotent on(repo, cluster)and ignoressuspended_at→ reports "already exists".invalid_targetas an enumeration guard → "no mirror at this URL".This PR makes the failure self-explanatory: when the clone probe is refused for a mirror we just confirmed exists, print the likely cause and the exact operator recovery command instead of the raw OAuth error.
What changed
auth: newErrRepoTargetUnknownsentinel;RepoScopedTokendetects an STSinvalid_targetviaerrors.Ason the typedsts.ExchangeError(xe.Code == "invalid_target") and wraps it with the sentinel, preserving the verbatim STS text via a second%w.repo mirror create:explainSuspendedMirrortranslates that sentinel into an actionable message +entire-core admin mirrors resume <id>, returning aSilentError. Unrelated probe errors (timeout, cancel, auth) pass through verbatim.Before
After
Detection — typed, not string-matched
Tests
TestRepoScopedToken_InvalidTarget— a 400invalid_targetSTS response surfaces asErrRepoTargetUnknown, with the verbatim detail preserved.TestExplainSuspendedMirror— renders the mirror id + resume command through the real wrapping chain; unrelated errors pass through untouched.Draft — open items
This is intentionally a draft; two things are still in flight:
sts.ExchangeError{StatusCode, Code, Description}lands in auth-go branchsts-typed-exchange-error, currently consumed here as a pseudo-version (v0.4.1-0.20260603110757-7103c3dc992a). Once that branch is merged and tagged v0.5.0, repointgo.modat the clean release. That auth-go build also raised the stdlib floor, which is why this PR bumps Go to 1.26.4 (also picks upGO-2026-5037/GO-2026-5039).invalid_targetis not exclusively suspension — the server collapses {absent, suspended, orphaned} into oneno mirror at this URL, and on a fresh create it's more likely propagation lag than suspension. Planned refinement: soften to "most likely suspended", surface the server's own description (xe.Description) verbatim, addentire-core admin mirrors listas a diagnostic, and possibly branch on thecreatedflag (fresh vs existing).Test plan
mise run lint— clean (golangci-lint 0 issues, gofmt/gomod/shellcheck pass)go build ./...+ targeted tests for the touched packages, against go 1.26.4go.modis repointed🤖 Generated with Claude Code
Note
Low Risk
CLI-only UX and error classification for mirror create; no auth protocol or server behavior changes beyond a dependency bump for typed STS errors.
Overview
When
entire repo mirror createreports an existing mirror but the clone readiness probe fails with STSinvalid_target, the CLI now explains the likely suspended placement case instead of a contradictory raw OAuth error.RepoScopedTokenmaps typedsts.ExchangeErrorwith codeinvalid_targettoErrRepoTargetUnknown, while keeping the original STS text in the error chain.explainSuspendedMirrorturns that into operator guidance (entire-core admin mirrors resume <id>) and aSilentErrorso the message is not duplicated; fresh creates still surface the raw error (propagation lag, not suspension).Tests cover
invalid_targetwrapping and the explain helper’s branches. auth-go is bumped to a pseudo-version with typed exchange errors; Go is updated to 1.26.4 ingo.modandmise.toml.Reviewed by Cursor Bugbot for commit 928dd85. Configure here.