Skip to content

fix(coderd/externalauth): support disabling token refresh retries - #26231

Merged
jscottmiller merged 3 commits into
mainfrom
jscottmiller/plat-293-refreshretries-flake
Jun 11, 2026
Merged

fix(coderd/externalauth): support disabling token refresh retries#26231
jscottmiller merged 3 commits into
mainfrom
jscottmiller/plat-293-refreshretries-flake

Conversation

@jscottmiller

Copy link
Copy Markdown
Contributor

TestRefreshToken/RefreshRetries flakes on Windows. The subtest disables transient-failure refresh retries by setting RefreshRetryTimeout = time.Nanosecond, but a near-zero timeout cannot deterministically prevent a retry: on coarse-clock platforms the 1ns deadline may not register as expired until after the first refresh attempt completes, and retry.Wait's first delay is zero, so an extra IDP refresh attempt slips through and the attempt-count assertion fails with refreshCount = totalRefreshes + 1.

A negative RefreshRetryTimeout now disables transient-failure retries explicitly so exactly one refresh attempt is made, and the test sets -1 instead of time.Nanosecond. The retry config fields are only set from tests, so default refresh behavior is unchanged.

Closes coder/internal#1550 (PLAT-293)

Root cause analysis
  1. The test sets RefreshRetryTimeout = time.Nanosecond intending "no retries".
  2. refreshTokenWithRetry creates context.WithTimeout(ctx, 1ns). On Linux this context is canceled synchronously at creation: consecutive time.Now() reads differ by more than 1ns, so context.WithDeadline observes time.Until(deadline) <= 0. The retryCtx.Err() != nil guard then deterministically stops after one attempt.
  3. On Windows, time.Now() is coarse, so both clock reads inside WithTimeout can return the same instant, and a real 1ns timer is scheduled instead of synchronous cancellation.
  4. The fake IDP is served in-process, so the first refresh attempt can complete before that timer fires. retryCtx.Err() is still nil and retry.Wait's first delay is zero, so a second refresh attempt happens.
  5. require.Equal(t, refreshCount, totalRefreshes) then fails with expected: 2, actual: 1 (or 4 vs 3 when the race hits a later loop iteration), matching all CI occurrences.

Timing-based test-side mitigations cannot close this race, so the fix adds explicit retry-disable semantics instead. RefreshRetries passed 100 consecutive local runs with the change.

This PR was generated by Coder Agents on behalf of @jscottmiller.

…hRetryTimeout

TestRefreshToken/RefreshRetries flaked on Windows because it disabled
transient-failure retries with RefreshRetryTimeout = time.Nanosecond.
A near-zero timeout is not deterministic: on coarse-clock platforms
the 1ns deadline may not register as expired until after the first
refresh attempt completes, and retry.Wait's first delay is zero, so
an extra IDP refresh attempt slips through and the attempt-count
assertion fails (refreshCount = totalRefreshes + 1).

Make a negative RefreshRetryTimeout disable transient-failure retries
explicitly so exactly one refresh attempt is made, and use -1 in the
test instead of time.Nanosecond.

Closes coder/internal#1550 (PLAT-293)
@linear-code

linear-code Bot commented Jun 10, 2026

Copy link
Copy Markdown

PLAT-293

@jscottmiller

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress | View chat
Requested: 2026-06-11 15:23 UTC by @jscottmiller
Spend: $20.11 / $100.00

deep-review v0.7.1 | Round 3 | cfb03f5..f6e6771

Last posted: Round 3, 4 findings (2 P4, 2 Nit), APPROVE. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P4 Author fixed (0cbd71a) externalauth.go:404 Inline comment duplicates doc comment and is verbose (5 lines, 2 suffice) R1 Gon P2, orchestrator P4 Yes
CRF-2 P4 Author fixed (0cbd71a) externalauth_test.go:186 Test comment re-explains coarse-clock rationale already in doc and inline comment R1 Gon P2, orchestrator P4 Yes
CRF-3 Nit Dropped by orchestrator (PR improved this comment by removing "(notably in tests)"; remaining verbosity predates this PR) externalauth.go:439 Comment block verbose R1 Gon P2 No
CRF-4 Nit Author fixed (0cbd71a) externalauth.go:391 Function doc lists two bypass paths but not the new negative-timeout bypass R1 Leorio Yes
CRF-5 Nit Author fixed (shortened PR title to 65 chars for squash merge) (commit subject) Commit subject is 82 chars, exceeds 72-char convention R1 Leorio Yes
CRF-6 Nit Dropped by orchestrator (diminishing returns; author refined this comment across 2 commits; 2-line form follows function's comment-per-early-return pattern; Gon acknowledges structural consistency) externalauth.go:404 Inline comment near-verbatim duplicates field doc R3 Gon No
CRF-7 Nit Dropped by orchestrator (diminishing returns; same reasoning as CRF-6; CRF-2 addressed verbosity, remaining brief restatement is style preference) externalauth_test.go:186 Test comment first sentence restates field doc R3 Gon No

Round log

Round 1

Panel (12 reviewers). 0 P0-P2, 2 P4, 3 Nit (1 dropped). Reviewed against cfb03f5..cbb23fa.

Round 2

Panel (6 reviewers). All R1 findings verified fixed. No new findings. Reviewed against cfb03f5..0cbd71a.

Round 3

Panel (5 reviewers). 0 open from prior rounds. 2 new Nit (both dropped as diminishing returns on already-addressed CRF-1/CRF-2). Reviewed against cfb03f5..f6e6771.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well-targeted fix for a Windows-specific flaky test. The root cause analysis correctly traces the nondeterminism to coarse clock resolution interacting with near-zero context timeouts, and the fix introduces an explicit retry-disable semantic rather than trying to make the timing tighter. 10 of 12 panel reviewers found no issues.

As Pariston put it: "I tried to build a case against this change and could not."

Severity summary: 2 P4, 3 Nit (1 Nit dropped as scope-adjacent).


coderd/externalauth/externalauth.go:391

Nit [CRF-4] This function doc lists two retry-bypass paths (permanent failures, no refresh token) but not the negative-RefreshRetryTimeout bypass this PR adds. The inline comment at line 404 covers the "why," but the function-level doc is the contract, and the contract is now incomplete.

Suggested addition (e.g. at the end of the existing doc comment):

...and the no-op case where no refresh token is set, and a negative RefreshRetryTimeout, all bypass the retry loop...

(Leorio)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/externalauth/externalauth.go Outdated
Comment thread coderd/externalauth/externalauth_test.go Outdated
Comment thread coderd/externalauth/externalauth.go
@jscottmiller jscottmiller changed the title fix(coderd/externalauth): disable refresh retries via negative RefreshRetryTimeout fix(coderd/externalauth): support disabling token refresh retries Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All R1 findings addressed. CRF-1/CRF-2 (verbose comments) condensed, CRF-4 (incomplete function doc) updated with the third bypass path, CRF-5 (commit subject length) fixed via PR title shortening for squash merge. 7 reviewers (Netero + 6 panel) verified the fixes and found no new issues.

As Pariston put it for the second round: "I tried to build a case against this change and could not."

🤖 This review was automatically generated with Coder Agents.

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@jscottmiller
jscottmiller marked this pull request as ready for review June 11, 2026 15:23

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Round 3: all prior findings remain fixed. The R3 delta (commit f6e6771) rewrites the inline comment at line 404 to describe the behavior directly instead of referencing the flake. Gon re-flagged residual duplication between the inline comments and the field doc (2 Nits), but the comments are now 2-3 lines each, structurally consistent with the function's comment-per-early-return pattern, and the author has consciously refined them across two commits. Dropping as diminishing returns.

6 reviewers (Netero + 5 panel) found no actionable issues. Pariston, for the third time: "I tried to build a case against this change and couldn't."

🤖 This review was automatically generated with Coder Agents.

@jscottmiller
jscottmiller merged commit 3da8226 into main Jun 11, 2026
33 checks passed
@jscottmiller
jscottmiller deleted the jscottmiller/plat-293-refreshretries-flake branch June 11, 2026 20:33
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flake: TestRefreshToken/RefreshRetries

2 participants