juanfont/headscale · error
not confirmed, aborting
Error message
not confirmed, aborting
What it means
Error (with body and redirectURL returned) from doLoginURLWithClient when followRedirects is true but the final status is not 200: the login flow followed redirects and landed on a non-OK page — meaning login did not complete (error page, 4xx/5xx after redirect, or a 3xx that was followed to something unexpected).
Source
Thrown at hscontrol/api/v1/nodes.go:25
"net/netip"
"slices"
"strconv"
"time"
"github.com/danielgtaylor/huma/v2"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
func init() {
registrations = append(registrations, registerNodes)
}
// errBackfillNotConfirmed guards BackfillNodeIPs behind explicit confirmed=true.
var errBackfillNotConfirmed = errors.New("not confirmed, aborting")
// registerMethodToV1Enum maps the stored register method onto the
// SCREAMING_SNAKE enum string the v1 contract emits.
var registerMethodToV1Enum = map[string]string{
util.RegisterMethodAuthKey: "REGISTER_METHOD_AUTH_KEY",
util.RegisterMethodOIDC: "REGISTER_METHOD_OIDC",
util.RegisterMethodCLI: "REGISTER_METHOD_CLI",
}
// Node mirrors the v1 Node message. The protojson contract emits unpopulated
// fields: scalars and slices always (no omitempty), nested messages and optional
// timestamps as JSON null when unset.
type Node struct {
ID string `format:"uint64" json:"id"`
MachineKey string `json:"machineKey"`
NodeKey string `json:"nodeKey"`
DiscoKey string `json:"discoKey"`
IPAddresses []string `json:"ipAddresses" nullable:"false"`View on GitHub (pinned to 565fd254d0)
Solutions
- Read the logged body (it is log.Printf'd) to see the provider's error message.
- Verify OIDC config: client_id, client_secret, redirect URL matching headscale's /oidc/callback.
- Check headscale and provider logs for the failing exchange.
- Confirm the flow accounts for the confirmation interstitial (see submitConfirmForm) if the deployment renders one.
Defensive patterns
Strategy: try-catch
Try / catch
body, redir, err := doLoginURLWithClient(hostname, loginURL, hc, true)
if err != nil {
if strings.Contains(err.Error(), "unexpected status code") {
// body and redir are populated; log them and inspect provider state
log.Printf("login failed, body=%s redirect=%v", body, redir)
}
return err
} Prevention
- Keep OIDC client credentials and redirect URIs correct and in sync with headscale config.
- Log the returned body on failure; it almost always names the cause.
- Handle the confirmation interstitial step where the deployment renders one.
When it happens
Trigger: OIDC interactive login where after following redirects the server returns an error page (401/403/500), or the flow ends on a redirect target that is not the success page.
Common situations: OIDC provider rejecting the client (bad client_id/secret), headscale OIDC callback mismatch (redirect URI not registered), expired nonce/state, or the interstitial confirmation step not completed.
Related errors
- tag must start with the string 'tag:'
- test pattern is required as first argument or use --test fla
- unexpected end of container wait
- auth request rejected
- tag should be lowercase
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/19cece66aa0493b8.
Report an issue: GitHub.