Watch
1
0
Fork
You've already forked RedFlag
0

agent: terminal backoff for dead credentials instead of retry loop

When ErrRefreshTokenInvalid or ErrMachineMismatch fires, the agent now
waits 10 minutes between poll attempts instead of exponential backoff.
These are permanent states — no amount of retrying fixes a dead
refresh token or a machine_id mismatch. The agent stays alive and
visible, waiting for operator intervention through the dashboard.
This commit is contained in:
Fimeg 2026-05-30 15:21:06 -04:00
commit fcabaefe82

View file

@ -245,6 +245,7 @@ func RunPollingLoop(loopCtx *LoopContext) error {
// Get commands from server
response, err := ctx.APIClient.GetCommands(ctx.Cfg.AgentID, metrics)
if err != nil {
terminalBackoff := false
if errors.Is(err, client.ErrMachineMismatch) {
// Terminal: the server no longer recognizes this host as the one the
// agent registered on — config moved or copied. Renewal can't fix it
@ -253,6 +254,7 @@ func RunPollingLoop(loopCtx *LoopContext) error {
// We keep polling rather than exit, so the agent stays visible and
// self-heals the moment an operator rebinds it server-side.
log.Printf("[ERROR] [agent] [auth] machine_id_mismatch identity_moved_or_copied re_registration_required agent_id=%s", ctx.Cfg.AgentID)
terminalBackoff = true
} else if errors.Is(err, client.ErrUnauthorized) && ctx.Cfg.RefreshToken != "" {
log.Printf("[INFO] [agent] [auth] jwt_expired attempting_renewal agent_id=%s", ctx.Cfg.AgentID)
renewErr := ctx.APIClient.RenewToken(ctx.Cfg.AgentID, ctx.Cfg.RefreshToken, version.Version)
@ -277,15 +279,21 @@ func RunPollingLoop(loopCtx *LoopContext) error {
// the agent needs re-registration. Surface it loudly; the client
// already buffered a critical refresh_token_invalid event.
log.Printf("[ERROR] [agent] [auth] refresh_token_invalid re_registration_required agent_id=%s error=%v", ctx.Cfg.AgentID, renewErr)
terminalBackoff = true
default:
// Transient renewal failure (network, 502). Fall through to backoff and retry.
log.Printf("[ERROR] [agent] [auth] token_renewal_failed error=%v", renewErr)
}
}
consecutiveFailures++
backoffDelay := calculateBackoff(consecutiveFailures, resolveBackoffBase(ctx.Cfg), resolveBackoffMax(ctx.Cfg))
log.Printf("[WARNING] Server unavailable (attempt %d), retrying in %s: %v", consecutiveFailures, backoffDelay, err)
var backoffDelay time.Duration
if terminalBackoff {
backoffDelay = 10 * time.Minute
log.Printf("[ERROR] [agent] [auth] terminal_state waiting_for_operator_intervention delay=%s agent_id=%s", backoffDelay, ctx.Cfg.AgentID)
} else {
backoffDelay = calculateBackoff(consecutiveFailures, resolveBackoffBase(ctx.Cfg), resolveBackoffMax(ctx.Cfg))
log.Printf("[WARNING] Server unavailable (attempt %d), retrying in %s: %v", consecutiveFailures, backoffDelay, err)
}
// Non-blocking stop check during backoff
if ctx.StopCh != nil {
select {