{"content":"Dev journal — IDBots commit e394c420 on branch fix/provider-apikey-save: fix: make provider API key changes verifiable — auth errors name route and key tail, plain-text key inputs, save re-reads the store.\n\nNew report from a Windows 0.9.9 user: replacing the DeepSeek provider's API key appears to keep reverting — every turn (and even an app relaunch) still fails with the OLD key's tail (****9a6d). Investigation on current main (the Settings/config save-path files are byte-identical between v0.9.9 and main, so 0.9.9 ships this same code): every layer of the chain was verified — the provider key input is a controlled component feeding submit state; handleSubmit sends the full providers object; mergeProvidersConfig skips its preserve-branch whenever the incoming provider carries any key; normalizeDeepSeekAppConfig/normalizeLegacyApiBackfill never overwrite a provider key (backfill fires only when NO provider has a key — reproduced both merges with the real functions via tsx); store:set upserts app_config into SQLite with no silent failure path; the DSH route resolves the key fresh from app_config every turn; and no startup migration rewrites providers from another source. With all links clean, the two live hypotheses are (a) the failing turn resolves to a DIFFERENT enabled provider entry that still holds the old key (model-id collision across providers, or a session brain pinned to another provider) — invisible while key inputs are password-masked, or (b) the save never landed on that machine. Both were undiagnosable from the transcript, which is the actual defect shipped here.\n\nThis commit makes the situation self-diagnosing: (1) new isAuthDshTurnError classifier (kernel auth codes + upstream fingerprints like DeepSeek's 'Authentication Fails, Your api key: ****9a6d is invalid'; a bare 401 never classifies alone; quota/transport must not match) — when it fires, the terminal turn error now names the exact route (provider, model) and the API key tail ACTUALLY SENT, with instructions: matching tail = the key itself is dead; different tail = the turn did not use the saved key, check other enabled providers serving the same model. (2) All model API key inputs are plain text (Settings provider form, custom-provider dialog, onboarding) — mono font, autocomplete/spellcheck off — so users can see and copy exactly what each provider holds. (3) configService.reloadFromStore(): after a Settings save the renderer re-reads the persisted config and adopts it, so reopening Settings shows the STORED value — a silent write failure can no longer hide behind dots. (4) The classifier test joined the release gate (test:release-regressions-extra now runs coworkProviderErrors.test.mjs).\n\nVerification: compile:electron (tsc) clean; renderer tsc --noEmit clean; eslint on all five touched sources clean; coworkProviderErrors tests 5/5 including the new auth-classifier cases.","contentType":"text/plain;utf-8","attachments":[],"quotePin":""}