test(desktop): archived-only reload E2E races with asynchronous session naming
- Dominant language
- TypeScript
- Stars
- 5.4k
- Forks
- 502
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 715
Description
## Summary
The `archived-only history boots into a usable new task` E2E test can reach its archive request while asynchronous session naming is still active.
On a fresh checkout of main at `93a8dd7852b268ddcce930ce6f28bf85dff4c955`, the unchanged test failed twice and passed once across three consecutive runs.
Both failures reported:
```text
RuntimeHostOperationError: Session has a live derived effect
```
Earlier diagnostic controls on `8b3655a1710a4c52b09759249d8c2dbb57284a7a` showed that waiting for naming allowed the complete original flow to pass. Those controls are reported separately below.
## Environment
- Latest tested commit: `93a8dd7852b268ddcce930ce6f28bf85dff4c955` (`main` at the start of retesting)
- OS: macOS 26.5.2, arm64
- Node.js: `v24.19.0`
- Installation and build: npm `11.19.0`
- Test command launcher: npm `11.17.0`
- Fresh detached worktree; Git status remained clean after testing
- `npm ci`: passed
- Complete `npm run build`: passed
- One Playwright worker, zero retries
- Disposable Electron profiles using the existing fake-backend E2E fixture
- Normal local network conditions
- No production code or original test modifications
A Node preload redirected `os.userInfo().homedir` to an existing disposable account directory. `HOME` was not changed.
## Reproduction
After installation and build, run from the repository root:
```bash
npm exec --workspace @maka/desktop -- \
playwright test \
--config e2e/playwright.config.ts \
--grep 'archived-only history boots into a usable new task' \
--repeat-each=3 \
--workers=1 \
--retries=0
```
The test waits for reply visibility before invoking archive:
```typescript
await expect(reply).toBeVisible({ timeout: 20_000 });
await page.reload();
await expect(reply).toBeVisible();
await page.evaluate(async () => {
const sessions = await window.maka.sessions.list();
for (const session of sessions) {
await window.maka.sessions.archive(session.id);
}
});
```
Failures occur during this archive call at `apps/desktop/e2e/new-task-reload.spec.ts:34`, before the post-archive bootstrap assertions.
The recorded runs used the following account-isolation preload:
```javascript
import os from 'node:os';
import { syncBuiltinESMExports } from 'node:module';
const userInfo = os.userInfo;
os.userInfo = (...args) => ({
...userInfo(...args),
homedir: '/absolute/path/to/disposable-account',
});
syncBuiltinESMExports();
```
The disposable directory was created before testing, and the preload was supplied through:
```bash
NODE_OPTIONS=--import=/absolute/path/to/isolate.mjs
```
These paths are placeholders for the reproduction environment.
## Latest-main results
Commit: `93a8dd7852b268ddcce930ce6f28bf85dff4c955`
| Test mode | Result |
| --- | --- |
| Unchanged archived-only E2E, three consecutive runs | 2 failed / 1 passed |
Both failures reported the same archive rejection:
```text
RuntimeHostOperationError: Session has a live derived effect
```
This confirms that the failure remains reproducible on the latest tested main, but is not deterministic.
## Earlier diagnostic controls
The following experiments were performed on `8b3655a1710a4c52b09759249d8c2dbb57284a7a`, not on the latest revision above.
| Test mode | Result |
| --- | --- |
| Unchanged archived-only E2E | 3 failed / 0 passed |
| Diagnostic: observe archive readiness with repeated requests | 0 failed / 3 passed |
| Control: wait for naming, then make one archive request | 0 failed / 3 passed |
### Archive-readiness diagnostic
A separate diagnostic copy recorded session names and retried archive approximately every 500 ms within a bounded observation window.
The first request was rejected in all three runs. Archive subsequently succeeded approximately 526 ms, 1,040 ms, and 4,109 ms after the first rejected request.
In each run, the session name changed from `New Chat` to `archived startup regression` before the successful request.
All remaining original assertions passed, including empty new-task bootstrap, sending a new message, and final counts of one archived session and one active session.
### Passive-wait control
To check whether repeated archive requests themselves affected recovery, another diagnostic copy waited for naming and then made exactly one archive request:
```typescript
await expect.poll(
async () => page.evaluate(async () => {
const sessions = await window.maka.sessions.list();
return sessions.length === 1 && sessions[0].name !== 'New Chat';
}),
{ timeout: 25_000 },
).toBe(true);
```
The observed naming waits were 909 ms, 2,919 ms, and 3,922 ms.
All three complete flows passed without archive retries.
This name check was a diagnostic for this fixture, not a proposed general-purpose readiness contract. A non-default name does not necessarily establish that every session effect has settled.
## Relevant code paths
- `session-retirement-coordinator.ts` intentionally rejects retirement while `sessionEffects.hasLiveSessionState(sessionId)` is true.
- `session-effect-coordinator.ts` tracks asynchronous title generation until the operation finishes.
- Title generation has a 15-second timeout.
- Desktop E2E substitutes the primary backend, while session naming uses a separately constructed auxiliary model-call path.
The fake primary reply can therefore be visible while naming is still active. Substituting the primary backend does not by itself make auxiliary naming deterministic.
## Impact
The test can fail while establishing its archived-only precondition, rather than while checking the bootstrap behavior it is intended to verify.
The evidence supports a race between the archive request and asynchronous naming. It does not establish a permanently blocked archive or a post-archive rendering defect.
Contributor guide
Assessment
This issue has not been assessed yet.