fix(sync): re-mint the workspace token on Plaud status -419#38
Conversation
Plaud rejects an expired workspace token two ways: HTTP 401, and HTTP 200
with `{status: -419, msg: "workspace token expired"}` in the body. Only the
401 path reached `PlaudAuthError`, so `runOnce`'s force-re-mint recovery
never fired for -419 and the poll failed with a generic error instead.
It could not self-heal either: `ensureFreshWorkspaceToken()` early-returns
while the cached WT's `exp` claim still looks fresh, but Plaud can begin
rejecting a WT long before that claim expires — leaving every poll failing
until the cached `exp` finally hit the refresh skew, up to ~24h later.
Map -419 to PlaudAuthError so the existing recovery re-mints and retries.
If that retry also fails, behaviour is unchanged: authRequired pauses for
a reconnect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The comment asserted the stuck window is "~24h", which is the JWT exp of one observed token, not a property of the API. State the real invariant instead: the poll stays broken while the cached tokenExp sits outside the refresh skew, because Plaud can reject a WT well before the exp it was minted with. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hi @lealwtf - Thanks for the PR. Which token were you using? There are three I'm aware of:
I've seen this 419 behavior before, but only when trying to use the UT/URT pulled from local storage (rather than api cookies). I believe what's happening is that you're grabbing the URT from the web session (rather than API cookies). You mint a new token using the URT and it gets an expiry of 24h. Then the web.plaud.ai session mints a new UT from the same URT and sees it's invalid because you already used it. So it re-auths, invalidates your entire chain of tokens, and creates a new URT/UT combo. The api.plaud.ai cookie approach outlined in the 0.5.11 instructions avoids that problem. I also tried what you have in this PR - retry on the 419, but it ends up "fighting" with the web plaud session and they both start minting new tokens every few minutes. Let me know what version/tokens you're using! |
|
Thanks — and your read looks right. Answering the question first, then correcting myself. Which tokens: 0.5.11 ( I have to walk back my "Testing" section. I claimed the poll recovered and stayed stable, and I'd started drafting a reply arguing that Then I actually checked instead of assuming. Chrome history: The web app was open at 16:59 — right between the two re-mints. And at 14:02:29 it hit I also can't tell you how often it re-minted, because the recovery path logs nothing on success. So your "both start minting every few minutes" is something this patch would do invisibly. That's a real flaw in it as written, independent of whether the root cause is the token source. The one thing I'd still like your read on, because it's the part I can't square with the api-vs-web framing. In my profile the cookies aren't host-scoped — querying Chrome for exactly what One row each, on Is Happy to close this. If you'd rather keep a version of it, I'd rework it to log the forced re-mint and refuse to force one if it already did within the last N minutes — pause with |

Problem
Plaud signals a rejected workspace token in two different ways, and the poller only recovers from one of them.
plaudFetchmaps HTTP 401 toPlaudAuthError, andPoller.runOnceuses that to force a one-shot WT re-mint and retry:But the list endpoint also rejects the token in the response body, with HTTP 200. Reproduced directly against the API with an expired WT:
HTTP 200 never reaches the 401 branch, so
pollAndProcessthrows a plainErrorhere:Since it isn't a
PlaudAuthError, the force-re-mint recovery is skipped andhandlePollErrorrecords it as a generic error.Why it doesn't self-heal
ensureFreshWorkspaceToken()(noforce) runs before every poll, but it early-returns while the cached WT still looks fresh:The catch is that the WT's JWT
expclaim and Plaud's actual acceptance of it are not the same thing. On my install a freshly minted WT carried anexp~24h out, and Plaud started returning-419for it about two minutes later. BecausetokenExpstill looked fresh, no re-mint was attempted — so every subsequent poll failed with-419for as long as the cachedexpstayed outside the refresh skew.Observed state while stuck:
{"lastError":"Plaud list returned status=-419 msg=workspace token expired","authRequired":false}authRequired: false, so the poller isn't paused either — it retries and fails every cycle. SinceensureFreshWorkspaceToken()won't re-mint and nothing forces it, the only ways out I could find were to wait for the cachedexpto reach the refresh skew (up to ~24h), or to stop the app, hand-edittokentonullin settings.json, and restart.I don't know why Plaud stops honouring a WT well before its
exp— that's server-side and I can't see it from here. Whatever the cause, the client-side gap is the same: a WT can be rejected whiletokenExpstill looks fresh, and today nothing re-mints in that window.Fix
Treat
-419like a 401 and reuse the recovery that already exists.PlaudAuthErroris already imported inpoller.ts, so this is oneif.If the forced re-mint also fails, behaviour is unchanged from any other auth failure:
handlePollErrorsetsauthRequiredand pauses for a reconnect.Testing
Verified on a first-party install that was stuck in the state above (
-419every cycle,authRequired: false):tokenExpmoved from the value that was taking-419to a newly minted one ~12 minutes later, which isensureFreshWorkspaceToken(true)running from the new branch.-419never reaches the log.audio_readyfired for every recording (all200inwebhook_log).pnpm -C server buildpasses.Context
I'm using Applaud to mirror recordings to disk and drive a local, offline transcription service off the
audio_readywebhook, instead of relying on cloud transcripts. That flow depends on the poller staying alive unattended, which is how I hit this: nothing synced for what would have been ~24h, with no indication anything was wrong except a repeating error in the Applaud log.Happy to adjust the approach — e.g. doing the mapping inside
plaudFetch/ the client layer instead of the poller so any endpoint benefits, or covering other negativestatusvalues if you know of more that mean "auth".-419is the only one I've observed.🤖 Generated with Claude Code