Shipped in 1.9.0 via #67's second commit e032d666. Two problems, one of them user-facing.
1. The stated cause is refuted
CHANGELOG.md says the cap is why CI was red on main. The 2x2 in #70 disproves it:
| code |
bun 1.3.14 |
bun 1.4.2 |
| pre-cap, pre-dual-read |
both stories fast |
wrong-cred 30,004 ms |
post-dual-read main |
signs-in 30,008 ms |
wrong-cred ~30,000 ms |
Capping a 30 s wait to 5 s moved the measured duration by 64 ms (29,955 ms capped vs 30,019 ms uncapped, same day), an uncapped commit ran 374 ms faster than the capped one, and a DEBUG=pw:api trace on main shows waitForLoadState resolving 8 ms after the click. The hang is not in that wait. The release notes for v1.9.0 carry a correction, but CHANGELOG.md still asserts it.
2. A slow but correct login is now reported as wrong credentials
capWait (src/adapters/budget.ts) only ever shortens, and no config knob raises it — src/config/schema.ts has no timeout field and start_debug({timeout}) can only shorten further. So the post-submit settle wait is a hard 5 s for every consumer, down from up to 30 s.
The wait starts right after adapter.click(submit), before anything has answered, so 5 s must cover the credentials POST round trip, server-side session creation, the redirect, and the navigation commit. Past that, samePage(after, loginUrl) still matches and the caller gets:
still on <url> after submitting ... the login did not take.
Check the credentials and the field keys
for credentials that are correct. Reachable on SSO and OAuth redirect chains, cold-start apps, and slow staging — which is the environment this tool is pointed at. It is also the DEFAULT path: the README's only persona example omits expect, and the expect branch is the one that keeps the full budget.
The comment justifying the constant says it covers "one redirect hop on an app that has already answered the login POST". The call order contradicts that; nothing has answered when the wait starts.
Neither of the two tests added with the cap pins this direction. Both assert the timeout VALUE handed to the adapter, and the fake waitFor returns immediately, so a slow-success regression passes them unchanged.
What to do
- Bound the failure path rather than the success path: poll up to the full budget for either the URL leaving
loginUrl or networkidle, whichever comes first. A rejected login is then caught as soon as the page settles, while a slow real redirect keeps its budget.
- Distinguish "did not navigate" from "did not navigate yet" in the error text.
- Add a test where the adapter's
waitFor rejects after the cap while the URL changes later, asserting performLogin resolves. That is what makes any chosen bound defensible.
- Correct or remove the CI attribution in
CHANGELOG.md.
Provenance
Found by two independent reviews during the 1.9.0 release, before #67 merged. The release proceeded because the dual-read in the same version unblocks developerz-ai/developerz.ai#2964; this is the follow-up that was promised rather than a reason to yank 1.9.0.
Refs #70.
Shipped in 1.9.0 via #67's second commit
e032d666. Two problems, one of them user-facing.1. The stated cause is refuted
CHANGELOG.mdsays the cap is why CI was red onmain. The 2x2 in #70 disproves it:wrong-cred30,004 msmainsigns-in30,008 mswrong-cred~30,000 msCapping a 30 s wait to 5 s moved the measured duration by 64 ms (29,955 ms capped vs 30,019 ms uncapped, same day), an uncapped commit ran 374 ms faster than the capped one, and a
DEBUG=pw:apitrace onmainshowswaitForLoadStateresolving 8 ms after the click. The hang is not in that wait. The release notes for v1.9.0 carry a correction, butCHANGELOG.mdstill asserts it.2. A slow but correct login is now reported as wrong credentials
capWait(src/adapters/budget.ts) only ever shortens, and no config knob raises it —src/config/schema.tshas no timeout field andstart_debug({timeout})can only shorten further. So the post-submit settle wait is a hard 5 s for every consumer, down from up to 30 s.The wait starts right after
adapter.click(submit), before anything has answered, so 5 s must cover the credentials POST round trip, server-side session creation, the redirect, and the navigation commit. Past that,samePage(after, loginUrl)still matches and the caller gets:for credentials that are correct. Reachable on SSO and OAuth redirect chains, cold-start apps, and slow staging — which is the environment this tool is pointed at. It is also the DEFAULT path: the README's only persona example omits
expect, and theexpectbranch is the one that keeps the full budget.The comment justifying the constant says it covers "one redirect hop on an app that has already answered the login POST". The call order contradicts that; nothing has answered when the wait starts.
Neither of the two tests added with the cap pins this direction. Both assert the timeout VALUE handed to the adapter, and the fake
waitForreturns immediately, so a slow-success regression passes them unchanged.What to do
loginUrlor networkidle, whichever comes first. A rejected login is then caught as soon as the page settles, while a slow real redirect keeps its budget.waitForrejects after the cap while the URL changes later, assertingperformLoginresolves. That is what makes any chosen bound defensible.CHANGELOG.md.Provenance
Found by two independent reviews during the 1.9.0 release, before #67 merged. The release proceeded because the dual-read in the same version unblocks developerz-ai/developerz.ai#2964; this is the follow-up that was promised rather than a reason to yank 1.9.0.
Refs #70.