Skip to content

fix(auth-service): "Another account" lands on email form even with login_hint#141

Merged
aspiers merged 9 commits intomainfrom
fix/another-account-skips-email
May 1, 2026
Merged

fix(auth-service): "Another account" lands on email form even with login_hint#141
aspiers merged 9 commits intomainfrom
fix/another-account-skips-email

Conversation

@aspiers
Copy link
Copy Markdown
Contributor

@aspiers aspiers commented May 1, 2026

Summary

  • Fixes "Another account" in account chooser skips straight to OTP form #138 — clicking "Another account" on the chooser now reliably reaches the email entry form, instead of the OTP form for the previous account, even when the OAuth flow that reached the chooser carried a login_hint.
  • Adds a new in-band signal (epds_skip_par_hint=1) set by pds-core's "Another account" rebind, so auth-service knows to ignore any PAR-stored login_hint on that specific path. prompt=login alone keeps its existing semantics (used by the auth-ui-guard's sign-in-view bounce, where the hint must still resolve and the OTP step must still render).
  • Hardens isForceLoginPrompt() to handle space-delimited (prompt=login consent) and array (prompt=['login']) forms via a shared oidc-prompt module, lifted from pds-core's existing parsePromptTokens so both packages consume the same canonical implementation.
  • Adds an E2E repro under features/session-reuse-bugs.feature (verified failing pre-fix, passing post-fix) plus route-level unit tests pinning the two distinguishable cases.

Why

pds-core/chooser-enrichment.ts's "Another account" rebind navigates back to auth-service/oauth/authorize, preserving the OAuth request_uri so the flow can resume after the new sign-in. The pre-fix rebind only appended prompt=login. That worked for shouldReuseSession() (which honoured prompt=login to skip the chooser redirect) but the login-page handler still resolved the original PAR-body login_hint, set initialStep='otp' and pre-filled with the previous account's email — exactly the bug from issue #138.

A first-cut fix gated initialStep / otpAlreadySent / email-prefill on isForceLoginPrompt(req). Local e2e revealed that broke the @session-reuse scenario "login_hint narrows to a stale binding on a multi-account device": pds-core's auth-ui-guard sign-in-view bounce ALSO appends prompt=login (to defeat session reuse) while still relying on the PAR-body login_hint resolving so the OTP step renders. The two paths needed disambiguating.

Fix

Two distinct signals, two distinct meanings:

  • prompt=login (OIDC standard, kept verbatim) — bypass session reuse. Set by both the chooser's "Another account" rebind AND the auth-ui-guard's sign-in-view bounce.
  • epds_skip_par_hint=1 (new, ePDS-private) — ignore any login_hint stored in the PAR for this request. Set ONLY by the chooser's "Another account" rebind.

In auth-service's /oauth/authorize handler, fetchParLoginHint is gated on the absence of epds_skip_par_hint=1. With the PAR hint suppressed and the URL login_hint already stripped by the rebind, resolvedEmail ends up null on the "Another account" path and the spec-correct rendering decision (hasLoginHintinitialStep) drops out: email form for issue #138, OTP form for the auth-ui-guard bounce. No forceEmailForm override needed.

isForceLoginPrompt() now delegates to a shared promptHasLogin() in @certified-app/shared, which correctly handles the OIDC space-delimited list shape and Express's array-of-strings shape from repeated query keys. The auth-ui-guard's existing parsePromptTokens was lifted to the shared module to keep one canonical implementation.

Test plan

  • New E2E scenario "Another account" with login_hint must reach the email form in features/session-reuse-bugs.feature — fails pre-fix, passes post-fix; tightened with email input is empty and OTP verification step is not active assertions.
  • Sibling scenarios still pass: "Another account" on the chooser goes to the email form (no-hint path), Signed-in user can sign in as a different account from the chooser, Login hint matches a bound account — chooser still wins, and crucially login_hint narrows to a stale binding on a multi-account device (pinned the case-B regression).
  • PR fix: graceful OAuth error when PAR has expired (no more raw JSON leak) #128's PAR-callback scenario Expired PAR shows a styled HTML page instead of leaking JSON still passes locally.
  • Route-level unit tests in packages/auth-service/src/__tests__/login-page-prompt-login.test.ts pin: "rebind path renders email step with empty input AND skips fetchParLoginHint" + "prompt=login without skip flag still resolves the PAR hint and serves OTP" + "no prompt=login still resolves hint normally". Verified by local sabotage.
  • session-reuse.test.ts extended for space-delimited + array prompt shapes.
  • Local: pnpm format:check, pnpm lint, pnpm typecheck, pnpm test (897/897), full local e2e in 3 phases (issue "Another account" in account chooser skips straight to OTP form #138 scenarios, PR fix: graceful OAuth error when PAR has expired (no more raw JSON leak) #128 PAR scenario, full session-reuse profile 20/20).
  • Manual smoke against the deployed preview after CI lights up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • "Another account" reliably opens a fresh email entry (no leftover prefilled email or immediate OTP) and skips PAR-derived hints when requested.
    • OIDC prompt=login is correctly recognized across repeated and space-delimited forms, ensuring a true fresh sign-in.

Copilot AI review requested due to automatic review settings May 1, 2026 13:41
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview, Comment May 1, 2026 4:44pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

📝 Walkthrough
📝 Walkthrough
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive All changes remain tightly scoped to issue #138: fixing prompt=login handling, account chooser redirection, and related test coverage. The PDS health-check retry logic is an incidental improvement unrelated to the core issue. Clarify whether the retry logic in e2e/step-definitions/common.steps.ts is necessary for the fix or a separate unrelated improvement that should be split into a dedicated PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: the 'Another account' feature now lands on the email form even when login_hint is present, which directly matches the primary change across the PR.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #138's requirements: shared OIDC prompt parsing [packages/shared/src/oidc-prompt.ts], session-reuse logic updated to respect prompt=login [packages/auth-service/src/lib/session-reuse.ts], auth-service login-page gated on isForceLoginPrompt [packages/auth-service/src/routes/login-page.ts], chooser skip-par-hint parameter [packages/pds-core/src/chooser-enrichment.ts], and new E2E scenario [features/session-reuse-bugs.feature].
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/another-account-skips-email

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 1, 2026

🦋 Changeset detected

Latest commit: 592a7a6

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@railway-app
Copy link
Copy Markdown

railway-app Bot commented May 1, 2026

🚅 Deployed to the ePDS-pr-141 environment in ePDS

Service Status Web Updated (UTC)
@certified-app/auth-service ✅ Success (View Logs) Web May 1, 2026 at 4:44 pm
@certified-app/pds-core ✅ Success (View Logs) Web May 1, 2026 at 3:52 pm
@certified-app/demo untrusted ✅ Success (View Logs) Web May 1, 2026 at 1:43 pm
@certified-app/demo ✅ Success (View Logs) Web May 1, 2026 at 1:42 pm

@coveralls-official
Copy link
Copy Markdown

coveralls-official Bot commented May 1, 2026

Coverage Report for CI Build 25223213611

Coverage increased (+2.0%) to 53.156%

Details

  • Coverage increased (+2.0%) from the base build.
  • Patch coverage: 8 uncovered changes across 1 file (8 of 16 lines covered, 50.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
packages/shared/src/oidc-prompt.ts 8 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 2806
Covered Lines: 1489
Line Coverage: 53.06%
Relevant Branches: 1709
Covered Branches: 911
Branch Coverage: 53.31%
Branches in Coverage %: Yes
Coverage Strength: 5.44 hits per line

💛 - Coveralls

Copy link
Copy Markdown

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

Fixes an auth-service /oauth/authorize edge case where clicking “Another account” from the enriched chooser could land users on the previous account’s OTP step when the original OAuth flow included a login_hint, by honoring prompt=login consistently in the login page step selection and prefill logic.

Changes:

  • Gate initialStep, otpAlreadySent, and email prefill on isForceLoginPrompt(req) so prompt=login reliably starts at the email step.
  • Add an E2E regression scenario covering the “Another account + login_hint” path.
  • Add a patch changeset describing the end-user impact.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/auth-service/src/routes/login-page.ts Honors prompt=login in step selection, OTP idempotency hinting, and email prefill suppression.
features/session-reuse-bugs.feature Adds an E2E repro scenario for “Another account” preserving login_hint while appending prompt=login.
.changeset/another-account-reaches-email-form.md Documents the patch-level behavioral fix for release notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/auth-service/src/routes/login-page.ts Outdated
Comment thread features/session-reuse-bugs.feature
Comment thread .changeset/another-account-reaches-email-form.md Outdated
Comment thread features/session-reuse-bugs.feature
aspiers added a commit that referenced this pull request May 1, 2026
Address PR #141 review feedback:

- Compute forceLogin up-front so fetchParLoginHint, resolveLoginHint
  and fetchDeviceAccountEmails are skipped on the "Another account"
  path. Those internal-API round trips were pure overhead — the result
  was unused for both rendering (initialStep / emailHint) and for
  shouldReuseSession (which already short-circuits on isForceLoginPrompt).
- Tighten the issue #138 E2E repro: assert #email is empty and
  #step-otp.active is hidden, not just that the email form is reachable.
  This pins down the full set of behaviours described in the issue.
- Drop the Client app developers section from the changeset — the fix
  requires no integration changes, and per the writing-changesets
  skill an audience-specific section is only worthwhile if the reader
  needs to adapt their code.
@aspiers aspiers force-pushed the fix/another-account-skips-email branch from 12d3a4b to b8b4d38 Compare May 1, 2026 13:52
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 13:52 Destroyed
Copilot AI review requested due to automatic review settings May 1, 2026 14:27
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 14:27 Destroyed
Copy link
Copy Markdown

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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/auth-service/src/routes/login-page.ts Outdated
Comment thread packages/auth-service/src/__tests__/login-page-prompt-login.test.ts Outdated
@aspiers aspiers requested review from Kzoeps and s-adamantine May 1, 2026 14:32
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 14:34 Destroyed
aspiers added a commit that referenced this pull request May 1, 2026
Address PR #141 review feedback (copilot, @aspiers).

The original isForceLoginPrompt() check `p === 'login'` only matched
the exact single-token literal. Per OIDC Core 1.0 §3.1.2.1, prompt is
a space-delimited list (e.g. `prompt=login consent`), and Express's
req.query parser surfaces repeated keys as arrays
(`?prompt=login&prompt=consent` -> `['login','consent']`). Either
shape would have re-introduced the issue #138 behaviour for clients
that pass them.

pds-core's auth-ui-guard already had a correct parsePromptTokens()
implementation (PR #129). Lift it to @certified-app/shared as
oidc-prompt.ts so both packages consume the same canonical version,
and rewrite isForceLoginPrompt to delegate to promptHasLogin. Keep
the auth-ui-guard names as thin re-exports of the shared functions
so existing imports (and the auth-ui-guard.test.ts test file) don't
churn.

Tighten the prompt=login test in login-page-prompt-login.test.ts:
the original assertion that fetchDeviceAccountEmails wasn't called
passed trivially because the request didn't carry a dev-id/ses-id
cookie pair, and that call is gated on cookie presence anyway. Send
a real cookie pair so the assertion now genuinely catches a
regression that re-introduced device-email fetching on the
prompt=login path. Verified via local sabotage: stripping the
forceLogin gates causes 2/3 tests to fail as expected.

Add session-reuse.test.ts coverage for the new behaviours: prompt
as space-delimited list, prompt as array, and the negative case
(list of non-login tokens).
Copilot AI review requested due to automatic review settings May 1, 2026 14:45
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 14:45 Destroyed
aspiers added 2 commits May 1, 2026 15:15
Address PR #141 review feedback (copilot, @aspiers).

The original isForceLoginPrompt() check `p === 'login'` only matched
the exact single-token literal. Per OIDC Core 1.0 §3.1.2.1, prompt is
a space-delimited list (e.g. `prompt=login consent`), and Express's
req.query parser surfaces repeated keys as arrays
(`?prompt=login&prompt=consent` -> `['login','consent']`). Either
shape would have re-introduced the issue #138 behaviour for clients
that pass them.

pds-core's auth-ui-guard already had a correct parsePromptTokens()
implementation (PR #129). Lift it to @certified-app/shared as
oidc-prompt.ts so both packages consume the same canonical version,
and rewrite isForceLoginPrompt to delegate to promptHasLogin. Keep
the auth-ui-guard names as thin re-exports of the shared functions
so existing imports (and the auth-ui-guard.test.ts test file) don't
churn.

Tighten the prompt=login test in login-page-prompt-login.test.ts:
the original assertion that fetchDeviceAccountEmails wasn't called
passed trivially because the request didn't carry a dev-id/ses-id
cookie pair, and that call is gated on cookie presence anyway. Send
a real cookie pair so the assertion now genuinely catches a
regression that re-introduced device-email fetching on the
prompt=login path. Verified via local sabotage: stripping the
forceLogin gates causes 2/3 tests to fail as expected.

Add session-reuse.test.ts coverage for the new behaviours: prompt
as space-delimited list, prompt as array, and the negative case
(list of non-login tokens).
… test

Address PR #141 review feedback (copilot).

The original beforeEach created its own `EpdsDb` against `dbPath`,
then `Object.defineProperty`-overwrote `ctx.db` to point at it. The
constructor-created instance was orphaned: never closed, holding a
file descriptor + a SQLite write lock + the WAL/SHM companion files
until process GC. Same dual-handle WAL antipattern that motivated
0f2b19d's switch from a second better-sqlite3 connection to reusing
PDS's Kysely instance — two writers on one file is opaque trouble.

The original "share state with the handler" rationale didn't apply:
no test in the file actually seeds or inspects via the test-side db
handle. Drop it. AuthServiceContext opens its own EpdsDb; we use
ctx.db (transitively) for everything. One handle per test, closed
cleanly by ctx.destroy().

Also tidy the afterEach: replace the empty-catch unlink with
fs.rmSync(force:true), and clean up the WAL/SHM companions so the
tmp directory isn't left with stale -wal/-shm files when WAL hasn't
checkpointed by close time.
Copilot AI review requested due to automatic review settings May 1, 2026 15:16
@aspiers aspiers force-pushed the fix/another-account-skips-email branch from 47a62e9 to 83e8012 Compare May 1, 2026 15:16
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 15:16 Destroyed
Copy link
Copy Markdown

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

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/auth-service/src/routes/login-page.ts Outdated
…r account"

Replace the prior approach that gated rendering on prompt=login with
a dedicated signal carrying the actual user intent.

The prior approach broke the @session-reuse e2e scenario "login_hint
narrows to a stale binding on a multi-account device": pds-core's
auth-ui-guard appends prompt=login on its sign-in-view bounce while
still expecting auth-service to honour the PAR's login_hint and
serve the OTP step. Conflating prompt=login with the "Another
account" rebind made auth-service skip hint resolution on that
bounce too, so no OTP email was sent and the user got stuck on an
empty email form.

Solution:
- pds-core's chooser-enrichment "Another account" rebind now sets
  epds_skip_par_hint=1 (in addition to the existing prompt=login
  for shouldReuseSession bypass) and drops any URL login_hint.
  Comment block updated to spell out why prompt=login alone is
  ambiguous and the skip flag is needed.
- auth-service's GET /oauth/authorize gates fetchParLoginHint on
  the absence of epds_skip_par_hint=1. Once the PAR hint is
  suppressed, resolvedEmail ends up null and the spec-correct
  rendering decision (hasLoginHint -> initialStep) drops out: email
  form for issue #138, OTP form for the auth-ui-guard bounce.
- Drop the forceEmailForm override and the unused isForceLoginPrompt
  import — no longer needed once the input to the rendering decision
  is correctly suppressed.

Unit tests in login-page-prompt-login.test.ts updated:
- "renders the email step on the Another account rebind path"
  asserts fetchParLoginHint is NOT called when epds_skip_par_hint=1.
- "honours PAR login_hint when prompt=login arrives without the
  skip flag" pins the auth-ui-guard bounce path so a future
  simplification doesn't re-conflate the two signals.

Verified locally:
- 20/20 session-reuse e2e (was 19/20 — the stale-binding scenario
  now passes again).
- 897/897 unit tests, lint/format/typecheck clean.
@blacksmith-sh

This comment has been minimized.

The CI e2e workflow already polls /health for up to 5 minutes per
service before invoking the suite, so by the time `pnpm test:e2e`
runs the deploy is reachable. But a single transient Railway-edge or
undici DNS blip 12 seconds later on the first scenario's `Given the
ePDS test environment is running` step kills an entire 9-minute e2e
run — observed on PR #141 commit dfee0f8.

Budget 6 attempts × 2s = ~12s of retries on transient failures
(undici TypeError, non-OK status). Real outages still surface once
the budget is spent.

Reproduced from CI logs: pre-step health check passed all 5 services
at 15:52:47, scenario 1 fetch threw `TypeError: fetch failed` at
15:52:59, all 60 subsequent scenarios passed.
Copilot AI review requested due to automatic review settings May 1, 2026 16:16
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 16:16 Destroyed
Copy link
Copy Markdown

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

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/auth-service/src/routes/login-page.ts
Comment thread packages/auth-service/src/__tests__/login-page-prompt-login.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/step-definitions/common.steps.ts`:
- Around line 29-41: The loop lacks a per-attempt fetch timeout so a hung
request can blow past the intended ~12s budget; update the fetch call inside the
retry loop to use AbortSignal.timeout(perAttemptMs) (perAttemptMs =
Math.ceil(12_000 / maxAttempts) or 2000) by passing the signal option to fetch
(replace fetch(url) with fetch(url, { signal: AbortSignal.timeout(perAttemptMs)
})); keep the existing error handling that assigns lastErr and backoff sleep
logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0adf3dd-acff-4982-b936-a53cc1c52bdf

📥 Commits

Reviewing files that changed from the base of the PR and between 47a62e9 and 15814d7.

📒 Files selected for processing (12)
  • .changeset/another-account-reaches-email-form.md
  • e2e/step-definitions/common.steps.ts
  • e2e/step-definitions/session-reuse.steps.ts
  • features/session-reuse-bugs.feature
  • packages/auth-service/src/__tests__/login-page-prompt-login.test.ts
  • packages/auth-service/src/__tests__/session-reuse.test.ts
  • packages/auth-service/src/lib/session-reuse.ts
  • packages/auth-service/src/routes/login-page.ts
  • packages/pds-core/src/auth-ui-guard.ts
  • packages/pds-core/src/chooser-enrichment.ts
  • packages/shared/src/index.ts
  • packages/shared/src/oidc-prompt.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/shared/src/index.ts
  • .changeset/another-account-reaches-email-form.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • e2e/step-definitions/session-reuse.steps.ts
  • packages/auth-service/src/lib/session-reuse.ts
  • features/session-reuse-bugs.feature
  • packages/shared/src/oidc-prompt.ts

Comment thread e2e/step-definitions/common.steps.ts
…und retry fetch

Address PR #141 review feedback (copilot, coderabbit).

- AuthServiceContext's setInterval was never cleared in destroy(),
  so vitest workers constructing one context per test would
  accumulate timers across tests. Store the handle, clearInterval()
  in destroy(), and unref() it so a short-lived test process isn't
  held alive by the timer alone if destroy() is somehow missed.
- The /health retry helper added in 15814d7 had no per-attempt
  fetch timeout, so a hung connection could blow past the
  documented ~12s budget. Add AbortSignal.timeout(2s) per attempt;
  the worst-case wall-clock is now bounded at 6 × (2s fetch + 2s
  backoff) = ~24s.
@railway-app railway-app Bot temporarily deployed to ePDS / ePDS-pr-141 May 1, 2026 16:43 Destroyed
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 1, 2026

@aspiers
Copy link
Copy Markdown
Contributor Author

aspiers commented May 1, 2026

(reply generated by Claude Code, model claude-opus-4-7)

Replying to coderabbit's "Out of Scope Changes" pre-merge check, which flagged the e2e/step-definitions/common.steps.ts retry helper as potentially unrelated to issue #138.

Fair flag, but the retry change is not unrelated — it was triggered by a CI failure that landed on this PR. Commit dfee0f8 passed all 14 static checks but the e2e (run) job failed at 9m26s with TypeError: fetch failed on the very first scenario's Given the ePDS test environment is running step (CI log: 15:52:59). The pre-step health-check loop in .github/workflows/e2e-tests.yml had passed all 5 services 12 seconds earlier. A single transient Railway-edge / undici DNS blip post-deploy-readiness was killing entire 9-minute e2e runs.

Splitting it into a separate PR would have left this PR's CI red on a flake unrelated to the actual fix, blocking merge and burning more CI cycles on rerun cycles. Adding it here keeps this PR's CI signal honest and prevents the same flake from blocking unrelated PRs in the same docker-compose / Railway environment until that separate PR landed.

The change itself is also tightly scoped — it only touches the Given step that already runs as the env-up health check on every scenario; nothing about the actual test logic changed. Subsequent commit 592a7a6 followed up on copilot's suggestion to add a per-attempt AbortSignal.timeout(2s) so the worst-case wall-clock is now bounded at ~24s.

If reviewers prefer this be split anyway, happy to revert here and re-land in a follow-up PR.

@blacksmith-sh

This comment has been minimized.

@aspiers
Copy link
Copy Markdown
Contributor Author

aspiers commented May 1, 2026

Tested to death and urgent need for a stable release -> merging as discussed in Telegram

@aspiers aspiers merged commit 84f7a93 into main May 1, 2026
16 of 17 checks passed
@aspiers aspiers deleted the fix/another-account-skips-email branch May 1, 2026 17:39
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.

"Another account" in account chooser skips straight to OTP form

2 participants