Skip to content

Explain suspended mirrors in entire repo mirror create#1344

Merged
nodo merged 3 commits into
mainfrom
nicer-suspended-mirror-error
Jun 3, 2026
Merged

Explain suspended mirrors in entire repo mirror create#1344
nodo merged 3 commits into
mainfrom
nicer-suspended-mirror-error

Conversation

@nodo
Copy link
Copy Markdown
Contributor

@nodo nodo commented Jun 3, 2026

https://entire.io/gh/entireio/cli/trails/497

Summary

When entire repo mirror create hits a suspended mirror placement, the output is baffling — it prints Mirror already exists and then fails with the raw OAuth error invalid_target: no mirror at this URL. Those read as contradictory.

They aren't. They come from two lookups that disagree about suspended rows:

  • create is idempotent on (repo, cluster) and ignores suspended_at → reports "already exists".
  • The clone probe's repo-scoped token exchange resolves the mirror through the data plane's auth gate, which hides suspended placements behind invalid_target as 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: new ErrRepoTargetUnknown sentinel; RepoScopedToken detects an STS invalid_target via errors.As on the typed sts.ExchangeError (xe.Code == "invalid_target") and wraps it with the sentinel, preserving the verbatim STS text via a second %w.
  • repo mirror create: explainSuspendedMirror translates that sentinel into an actionable message + entire-core admin mirrors resume <id>, returning a SilentError. Unrelated probe errors (timeout, cancel, auth) pass through verbatim.
  • deps / toolchain: pulls the typed-error auth-go build and bumps Go to 1.26.4 (see open item use github runners #1).

Before

Mirror already exists (01KS6KFJR2XS6PZ188MVYE07AN)
  entire://aws-us-east-2.entire.io/gh/entireio/entire-upgrade
authorize clone probe: repo-scoped token exchange: token exchange: status 400: invalid_target: no mirror at this URL

After

Mirror already exists (01KS6KFJR2XS6PZ188MVYE07AN)
  entire://aws-us-east-2.entire.io/gh/entireio/entire-upgrade

Mirror 01KS6KFJR2XS6PZ188MVYE07AN is registered but the cluster won't issue clone tokens for it.
This usually means the placement is suspended after upstream GitHub access
was lost (App uninstalled, the repo went private, or a transient API error).
An operator can re-enable it once access is restored:
  entire-core admin mirrors resume 01KS6KFJR2XS6PZ188MVYE07AN

Detection — typed, not string-matched

var xe *sts.ExchangeError
if errors.As(err, &xe) && xe.Code == "invalid_target" {
    return "", fmt.Errorf("repo-scoped token exchange: %w: %w", ErrRepoTargetUnknown, err)
}

Tests

  • TestRepoScopedToken_InvalidTarget — a 400 invalid_target STS response surfaces as ErrRepoTargetUnknown, 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:

  1. auth-go release (pseudo-version → v0.5.0). The typed sts.ExchangeError{StatusCode, Code, Description} lands in auth-go branch sts-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, repoint go.mod at 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 up GO-2026-5037 / GO-2026-5039).
  2. Wording precision. invalid_target is not exclusively suspension — the server collapses {absent, suspended, orphaned} into one no 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, add entire-core admin mirrors list as a diagnostic, and possibly branch on the created flag (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.4
  • Manual repro against a suspended mirror once auth-go v0.5.0 is tagged and go.mod is 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 create reports an existing mirror but the clone readiness probe fails with STS invalid_target, the CLI now explains the likely suspended placement case instead of a contradictory raw OAuth error.

RepoScopedToken maps typed sts.ExchangeError with code invalid_target to ErrRepoTargetUnknown, while keeping the original STS text in the error chain. explainSuspendedMirror turns that into operator guidance (entire-core admin mirrors resume <id>) and a SilentError so the message is not duplicated; fresh creates still surface the raw error (propagation lag, not suspension).

Tests cover invalid_target wrapping 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 in go.mod and mise.toml.

Reviewed by Cursor Bugbot for commit 928dd85. Configure here.

Copilot AI review requested due to automatic review settings June 3, 2026 11:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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: Introduces ErrRepoTargetUnknown and maps STS invalid_target token-exchange failures to it (while preserving the original STS error text in the error chain).
  • repo mirror create: Adds explainSuspendedMirror handling so this specific auth failure is rendered as a clear “registered but not servable” message plus the operator resume command, returning SilentError to 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.

Comment thread cmd/entire/cli/repo_mirror.go
@nodo nodo force-pushed the nicer-suspended-mirror-error branch from cba5e28 to 5d735a7 Compare June 3, 2026 13:07
nodo and others added 3 commits June 3, 2026 15:26
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
@nodo nodo force-pushed the nicer-suspended-mirror-error branch from fe609ca to 928dd85 Compare June 3, 2026 13:31
@nodo nodo marked this pull request as ready for review June 3, 2026 13:40
@nodo nodo requested a review from a team as a code owner June 3, 2026 13:40
@nodo
Copy link
Copy Markdown
Contributor Author

nodo commented Jun 3, 2026

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ 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.

@nodo nodo merged commit 06df606 into main Jun 3, 2026
10 checks passed
@nodo nodo deleted the nicer-suspended-mirror-error branch June 3, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants