Skip to content

fix(server): clear auth cleanup intervals to prevent flaky test exits#5576

Merged
bokelley merged 2 commits into
adcontextprotocol:mainfrom
garvitkaushik-123:fix/flaky-cross-tenant-test-interval-cleanup
Jun 25, 2026
Merged

fix(server): clear auth cleanup intervals to prevent flaky test exits#5576
bokelley merged 2 commits into
adcontextprotocol:mainfrom
garvitkaushik-123:fix/flaky-cross-tenant-test-interval-cleanup

Conversation

@garvitkaushik-123

Copy link
Copy Markdown
Contributor

Summary

Fixes the flaky require-admin-cross-tenant.test.ts that fails under full-suite parallelism but passes in isolation (#5393).

Root cause: The three setInterval timers started by startBackgroundCleanup in auth.ts (session cache, DB refresh, ban cache) are never cleared. When vitest exits while an interval callback is still in-flight, the worker can produce a corrupted response on the affected test.

Fix:

  • startBackgroundCleanup now returns the timer handle instead of void
  • All three timer handles are captured at module scope
  • New stopAuthTimers() export calls clearInterval on all three
  • Test adds afterAll(() => stopAuthTimers()) for clean teardown

Non-breaking — adds one optional export to auth.ts, no protocol surface change.

Test plan

  • vitest run server/tests/unit/require-admin-cross-tenant.test.ts — 16/16 pass
  • vitest run --no-file-parallelism server/tests/ — full sequential suite to confirm the race is gone
  • tsc --noEmit — compiles clean

Closes #5393

The three `setInterval` timers in auth.ts (session cache, DB refresh,
ban cache) were never cleared. When vitest exits while an interval
callback is still in-flight, the worker can produce a corrupted response
and flake the `require-admin-cross-tenant` test.

Capture each timer handle from `startBackgroundCleanup` and export a
`stopAuthTimers()` helper so test teardown can clear them cleanly.

Closes adcontextprotocol#5393

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aao-ipr-bot

aao-ipr-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

IPR Policy Agreement Required

@garvitkaushik-123 — thanks for the contribution. Before this PR can be merged, the AgenticAdvertising.Org IPR Policy requires your agreement.

To agree, post a new comment on this PR with the exact phrase:

I have read the IPR Policy

Your signature is recorded once and covers all contributions to AAO repositories. See signatures/README.md for what gets recorded and why.

@aao-release-bot aao-release-bot 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.

⚠️ Argus review could not complete

The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final gh pr review). A human reviewer should take this PR.

View workflow run

This is an automated message from the Argus AI review workflow.

@garvitkaushik-123

Copy link
Copy Markdown
Contributor Author

I have read the IPR Policy

@aao-ipr-bot

aao-ipr-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

IPR Policy — signed

Thanks, @garvitkaushik-123. Your agreement to the IPR Policy is recorded at signatures/ipr-signatures.json and applies to all AAO repositories.

@bokelley bokelley 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.

Reviewed for triage cleanup: narrow, green, and no 3.2-triggering changeset.

@bokelley
bokelley enabled auto-merge (squash) June 25, 2026 15:58

@aao-release-bot aao-release-bot 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.

Clean teardown fix. Production behavior is unchanged — the timers were already unref()'d, so this only buys a deterministic vitest exit, which is the right shape for a flaky-exit fix.

Things I checked

  • Timer capture is sound. startBackgroundCleanup returns ReturnType<typeof setInterval> (auth.ts:67), three handles captured into module-scope consts (auth.ts:75, 99, 117), and stopAuthTimers (auth.ts:127) is declared after all three are initialized — no TDZ. clearInterval is the correct API for a setInterval handle.
  • No auth-path impact. security-reviewer: no security issue. The diff touches only the cleanup-timer factory and adds the clearer — validation, cache keys (still per-cookie/per-token hashes), and tenant scoping are untouched.
  • Stopping cleanup cannot bypass auth. Every read path re-checks expiresAt > now at use: ban cache (auth.ts:132), bearer JWT (auth.ts:346), session cache (auth.ts:905, 1884), dead-session cache (auth.ts:889-891). The timers are memory reclamation, not correctness-bearing. A lifted ban still expires by TTL within 60s regardless of cleanup.
  • No unbounded-growth DoS if cleanup is ever stopped. Independent size caps exist: banCache.size > 10_000 full-clear (auth.ts:137), DEAD_SESSION_MAX_SIZE = 50_000 (auth.ts:48). Stopping the timer only delays reclamation.
  • No changeset needed. Server app code + test only, no protocol/wire surface. Correct to omit.
  • afterAll(() => stopAuthTimers()) registered at file top level (require-admin-cross-tenant.test.ts:33) — runs once per file, correct scope.

Test-plan honesty

The box that actually confirms the fix — vitest run --no-file-parallelism server/tests/ — is unchecked. The isolated 16/16 run and tsc --noEmit are green, but the full sequential suite is the run that proves the cross-test race is gone, and it's the primary thing this PR claims to fix. The production-side change is verified inert by both experts, so this isn't a block — but the race-fix itself ships unvalidated against the path it targets. Run that box before merge.

Follow-ups (non-blocking — file as issues)

  • The DB-refresh timer is still fire-and-forget. cleanExpiredRefreshes().then(...) (auth.ts:99-105) is not awaited. clearInterval stops new invocations but cannot cancel a query already in flight at afterAll. The 10-minute interval plus unref() makes a mid-flight hit very unlikely, so this materially reduces the race — but doesn't provably eliminate every variant. (code-reviewer: Medium, partial-but-right-mechanism.)
  • This only covers one test file. A sibling unit test that imports auth.js and exits under parallelism would still observe the timers. Worth a shared teardown helper if the pattern recurs.

Minor nits (non-blocking)

  1. Document the intent. Add a one-line comment at auth.ts:127 marking stopAuthTimers() as a test/shutdown teardown helper, not a runtime toggle — it's an exported function whose only safe caller is teardown.

Approving on the strength of two clean expert verdicts plus unchanged production behavior. Close the --no-file-parallelism box before you land it.

@bokelley
bokelley merged commit 87564ff into adcontextprotocol:main Jun 25, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky: require-admin-cross-tenant.test.ts fails under full-suite parallelism, passes in isolation

2 participants