Fix login reliability and client timeout causing duplicate writes - #36
Merged
Merged
Conversation
Three related failures made browser-backed commands unreliable, and one of them silently duplicated user data. 1. Kernel client HTTP timeout (the data-loss one) The SDK's client-side timeout defaults to 60s, but automations are dispatched with timeout_sec up to 120s (addCustomFood). The client gave up while the browser was still working, so Cronometer committed the change while crono reported "Request to Kernel API timed out". Every retry then created another copy - logging one item three times produced three identical custom foods. Set an explicit client timeout above the largest timeout_sec. The browser session budget had the same shape of bug: 120s when auto credentials exist, but a command can spend ~60s logging in before a 120s automation starts, so the session could expire mid-operation. Use a single 300s budget. 2. Login navigated via the marketing homepage Login loaded cronometer.com and clicked the header "Log In" link. That click frequently does not navigate, and because the loop recorded a successful *click* it also suppressed the direct-navigation fallback below it. The form was never reachable and login failed with "Could not find email input on https://cronometer.com/". Navigate straight to /login/. 3. Login verification raced the app boot Success was inferred from the absence of login UI after a 500ms wait, which is shorter than the GWT app's swap from login form to app shell. Logins that had actually succeeded reported "Login verification failed". Wait for the shell to settle and confirm with a positive signal - that the diary renders - reusing the existing check, now extracted as buildDiaryPresentedCheckCode() and shared with buildLoginCheckCode(). Also clarify the auto-login error message. When Cronometer throttles logins it serves the logged-out marketing page with no error text, which surfaced as "Your credentials may be incorrect" and invited a retry loop that extended the throttle. Verified end-to-end against a live account: `crono recipes` now logs in headlessly on the first attempt and returns results. Co-authored-by: Dan <miller0daniel@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related failures made browser-backed commands unreliable. One of them silently duplicated user data.
I hit these while logging a single meal: it took ~90 minutes and left three identical custom foods in my Cronometer account. Each fix is verified against a live account.
1. Kernel client HTTP timeout — the data-loss one
@onkernel/sdk's client-side HTTP timeout defaults to 60s, but automations are dispatched withtimeout_secup to 120s (addCustomFood). The client aborts while the browser is still working, so Cronometer commits the change while crono reports failure:That error is indistinguishable from a genuine failure, so the natural response is to retry — and each retry creates another copy. Three "failed" attempts left three identical custom foods.
Fixed by giving the SDK an explicit client timeout comfortably above the largest
timeout_sec.The browser session budget had the same shape of bug:
timeout_seconds: hasAutoCredentials ? 120 : 300, but a command can spend ~60s logging in before a 120s automation starts, so the session could expire mid-operation. Replaced with a single 300s budget.2. Login navigated via the marketing homepage
buildAutoLoginCodeloadedcronometer.comand clicked the header "Log In" link. That click frequently does not navigate — and because the loop setclickedLogin = trueon a successful click rather than a successful navigation, it also suppressed the direct-navigation fallback immediately below it. The form was never reachable:Fixed by navigating straight to
/login/. In my testing this was the single biggest source of flakiness — it failed roughly 2 out of every 3 runs.3. Login verification raced the app boot
Success was inferred from the absence of login UI after a
waitForTimeout(500). That is shorter than the GWT app's swap from login form to app shell, so logins that had actually succeeded reported:Fixed by letting the shell settle and confirming with a positive signal — that the diary actually renders. This reuses the check
buildLoginCheckCodealready performed, now extracted asbuildDiaryPresentedCheckCode()and shared by both.Also: a misleading error message
When Cronometer throttles logins it serves the logged-out marketing page with no error text, so
loginErroris null and the existing rate-limit detection (which greps page text for "too many" etc.) doesn't fire. It surfaced as "Your credentials may be incorrect", which invites exactly the retry loop that extends the throttle. The message now names throttling as the likely cause when credentials are known good.Worth noting for anyone debugging this: while throttled, every selector-based failure downstream reports something misleading —
Could not find meal category "Lunch" in diary,No food found matching "..."— because the automation is running against the marketing page. Fixes 2 and 3 mean login now fails loudly at the login step instead of letting a logged-out session proceed.Testing
npm test— 221 passing (addedtests/kernel/client.test.ts, updatedtests/kernel/login.test.ts)npm run lint,npm run build, Prettier check — all cleancrono recipesnow logs in headlessly on the first attempt and returns results. Before these changes the same command failed repeatedly at login.One test changed meaning rather than just being updated:
"should start at cronometer.com and click login link"asserted the behavior in fix 2, so it is replaced by a test asserting direct navigation and one asserting the homepage-click path is gone.Note on headless
I did not change the
headless: hasAutoCredentialsbehavior. Headless was never the problem — with fix 2 applied, headless login works reliably. Flagging it because "Cronometer hangs headless sessions" is an easy conclusion to draw from the symptoms of fix 2, and forcing a headed browser is a slower workaround for a bug that is fixed here.🤖 Generated with Claude Code