What this prevents
- I can launch the desktop app and have the window open already authenticated as the local user.
- I can use the desktop client without configuring Google OAuth or manually creating an account.
- I can close and relaunch the app and not have to log in again.
Every desktop launch should auto-inject a local user JWT into the webview so the user lands on the dashboard. Today the host's reqwest call to the desktop local-auth endpoint fails on every attempt for a reason that does not surface in the logs (the error chain is empty). Combined with the sidecar emitting TESSLATE_READY before uvicorn actually binds, the host can spend its retry budget before the sidecar is even accepting connections. The frontend has no fallback path. The result: every launch lands on the manual-login page, and Google login then fails because no GOOGLE_CLIENT_ID is configured by default. The user effectively cannot use the app without manual setup.
Surface
Two contributing root causes share the same user-facing symptom.
1. Persistent reqwest failure from the host
- The desktop host's
fetch_local_user_token fails every iteration even AFTER uvicorn is bound. Verified by curl: curl http://127.0.0.1:43111/api/desktop/local-auth returns 401 in 3 ms while the host's retry loop is still failing in the same process.
- The same reqwest config used by the tray succeeds for
/api/desktop/tray-state. Both create a fresh client with the same timeout, both target 127.0.0.1:43111, both pass an Authorization: Bearer ... header. The only behavioral difference is polling cadence (5 s vs 500 ms) and which endpoint they hit.
- Error message from the host's reqwest has no underlying cause:
error sending request for url (http://127.0.0.1:43111/api/desktop/local-auth) with no : ... suffix. This suggests reqwest::Error::source() returns None, which is unusual for a connect or send failure.
2. Sidecar emits readiness too early
- The sidecar prints
TESSLATE_READY from its entrypoint before uvicorn binds the listening socket, so the host begins its bounded retry budget against a port that is not yet accepting connections. Whatever extra latency uvicorn needs to start eats into that budget.
Expected vs Actual
Expected: on launch, the host obtains a local-auth token within the retry budget, injects it into the webview, and the user lands on the dashboard.
Actual: the host exhausts the retry loop and falls back to the manual-login page. Google login fails because no client id is configured. The app appears unusable.
Suggested fix
Two independent changes; either alone improves the situation, both together fix it.
- Make the sidecar emit
TESSLATE_READY after uvicorn's startup hook fires (or after a successful self-loopback GET /healthz), not before. The bound socket is what the host actually needs.
- Log
reqwest::Error::source() chain and is_connect() / is_timeout() on every failed iteration so the empty error message stops hiding the real cause. Investigate why the same client config works for the tray endpoint and not for local-auth.
Fallback UX: if the host gives up, show a "Couldn't reach sidecar" screen with a Retry button rather than dropping the user into the manual-login form.
What this prevents
Every desktop launch should auto-inject a local user JWT into the webview so the user lands on the dashboard. Today the host's reqwest call to the desktop local-auth endpoint fails on every attempt for a reason that does not surface in the logs (the error chain is empty). Combined with the sidecar emitting
TESSLATE_READYbefore uvicorn actually binds, the host can spend its retry budget before the sidecar is even accepting connections. The frontend has no fallback path. The result: every launch lands on the manual-login page, and Google login then fails because noGOOGLE_CLIENT_IDis configured by default. The user effectively cannot use the app without manual setup.Surface
Two contributing root causes share the same user-facing symptom.
1. Persistent reqwest failure from the host
fetch_local_user_tokenfails every iteration even AFTER uvicorn is bound. Verified by curl:curl http://127.0.0.1:43111/api/desktop/local-authreturns 401 in 3 ms while the host's retry loop is still failing in the same process./api/desktop/tray-state. Both create a fresh client with the same timeout, both target127.0.0.1:43111, both pass anAuthorization: Bearer ...header. The only behavioral difference is polling cadence (5 s vs 500 ms) and which endpoint they hit.error sending request for url (http://127.0.0.1:43111/api/desktop/local-auth)with no: ...suffix. This suggestsreqwest::Error::source()returnsNone, which is unusual for a connect or send failure.2. Sidecar emits readiness too early
TESSLATE_READYfrom its entrypoint before uvicorn binds the listening socket, so the host begins its bounded retry budget against a port that is not yet accepting connections. Whatever extra latency uvicorn needs to start eats into that budget.Expected vs Actual
Expected: on launch, the host obtains a local-auth token within the retry budget, injects it into the webview, and the user lands on the dashboard.
Actual: the host exhausts the retry loop and falls back to the manual-login page. Google login fails because no client id is configured. The app appears unusable.
Suggested fix
Two independent changes; either alone improves the situation, both together fix it.
TESSLATE_READYafter uvicorn's startup hook fires (or after a successful self-loopbackGET /healthz), not before. The bound socket is what the host actually needs.reqwest::Error::source()chain andis_connect()/is_timeout()on every failed iteration so the empty error message stops hiding the real cause. Investigate why the same client config works for the tray endpoint and not for local-auth.Fallback UX: if the host gives up, show a "Couldn't reach sidecar" screen with a Retry button rather than dropping the user into the manual-login form.