You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: let a slow add-source wizard finish instead of failing with a phantom disk error (#291)
## The bug (diagnosed from a user diagnostic bundle)
Creating a backup source failed every time with **"Driven hit a disk
error reading a file"** — with zero real disk errors anywhere and **zero
trace of the six failed `add_source` calls in the logs**. Root cause
chain:
1. `add_source` resolved the local folder from a single-use dialog token
with a **5-minute TTL**. A real wizard session (big-tree exclusion
preview + Drive destination browsing + thinking) outlives that, so
Finish always failed.
2. `take_dialog_token` consumed the token **before** any validation, so
the first failed Finish destroyed it and every retry in the same session
failed too.
3. Both failure modes were mapped to `local.io_error`, whose copy tells
the user their **disk** is failing.
4. Command rejections were never logged, so the diagnostic bundle
contained nothing to diagnose with.
## The fix
- **`add_source` peeks up front and spends the token only at commit**
(the same R3-P2-1 pattern the restore command already uses), so failed
validation leaves the token intact.
- **`DIALOG_TOKEN_TTL` 5 min → 60 min**; the binding now stores its
expiry instant (testable without `Instant` underflow).
- **New stable code `internal.stale_dialog_token`** (SPEC §24 + en-US
copy "pick the folder again") replaces the `local.io_error` mapping for
unknown/spent/expired tokens across `add_source`, exclusion previews,
restore, and the diagnostic export. The dialog-*cancel* sentinel is
untouched.
- **Every `CommandError` is now logged (WARN) as it serialises across
the IPC boundary** — the single seam every rejection passes through — so
failures land in the rolling log and diagnostic bundles. Known
trade-off: a UI polling loop against a persistently failing command
writes one WARN per poll; accepted for diagnosability.
- **Wizard error UI**: the add-source and setup wizards render a muted,
monospace technical-detail line (stable code + redacted backend message)
under the localized error.
## ⚠️ Deliberate amendment to the R8-P2-1 contract
R8-P2-1 said backend English must never render. This PR **narrows**
that: the localized `t(errors.${code}.long)` string remains the only
primary error, but a dedicated muted detail element may carry the stable
code + backend `message`. Rationale: two failures sharing one code
(expired token vs. genuinely unreadable folder) were indistinguishable
on screen, which is how a token expiry masqueraded as a disk error. The
`recovery-reveal-error-i18n` test is updated to enforce the new shape
(primary line exactly localized; backend text only inside
`*-error-detail`).
## Tests
- `cargo check --workspace --all-targets` clean; `cargo fmt --check`
clean (dockerized); driven-core 555 + driven-app 417 lib tests +
ipc_path_validation pass
- New: token-expiry rejection test
(`expired_dialog_token_is_rejected_by_peek_and_take`), stale-token i18n
copy test, detail-line rendering assertions
- UI: eslint 0 errors, prettier clean, vue-tsc clean, 778 vitest pass
README checked, no changes needed (no stale claims; behavior-level fix
below the feature list).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01LRMdu3VkuhL6Ny6hRcnFpU
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|`internal.stale_dialog_token`| A backend-minted dialog token (§11.6.1 C1) was unknown, already spent, or past its TTL — re-open the native picker and retry. Distinct from `local.io_error`: the disk is fine |
1824
1825
1825
1826
Frontend maps these to user-friendly messages via
1826
1827
`t('errors.${code}.short')` and `t('errors.${code}.long')` per DESIGN
0 commit comments