feat: [ENG-2494] FileTaskHistoryStore: Stale recovery on read#562
feat: [ENG-2494] FileTaskHistoryStore: Stale recovery on read#562bao-byterover merged 1 commit intoproj/persis-task-historyfrom
Conversation
|
Claude finished @bao-byterover's task in 3m 24s —— View job PR Review: feat: [ENG-2494] FileTaskHistoryStore: Stale recovery on read
SummaryScore: 4/5 Solid, well-scoped implementation of stale recovery. The design choices are sound: best-effort persistence (both writes catch their own failures), defensive idempotency in Two inline comments have been posted. Remaining notes below: Issues
Concurrent File header comment still lists stale recovery as future work (nitpick)
Positives
|
| return undefined | ||
| } | ||
|
|
||
| if (this.isStale(entry.status, entry.createdAt, Date.now())) { |
There was a problem hiding this comment.
nitpick: Date.now() is called twice in quick succession — once for isStale and once for buildRecovered. In the (extremely unlikely) event they straddle a millisecond boundary, completedAt will be slightly later than the staleness check. Capturing once is cleaner:
| if (this.isStale(entry.status, entry.createdAt, Date.now())) { | |
| const now = Date.now() | |
| if (this.isStale(entry.status, entry.createdAt, now)) { | |
| const recovered = this.buildRecovered(entry, now) | |
| await this.persistRecovery(recovered) | |
| return recovered | |
| } |
| ) | ||
| }), | ||
| ]) | ||
| this.indexCache = undefined |
There was a problem hiding this comment.
issue (concurrency edge case): this.indexCache = undefined is set after the Promise.all resolves, but during the two async writes, another concurrent call to readIndexDedup could arrive. Since the cache is already cleared by line 278 only after the awaits complete, the concurrent call would find this.indexCache still set to the in-progress-but-not-yet-committed-to-disk state.
Actually more subtle: during recoverViaTaskId → persistRecovery → await Promise.all(...), control can yield. A concurrent readIndexDedup call sees this.indexCache === undefined (cleared from a previous persistRecovery) and kicks off a fresh index read — which may not yet have the recovery line appended. This means two recovery passes could race.
Since the recovery is explicitly best-effort and the idempotency guard in recoverViaTaskId (line 349) handles partial-success data files safely, this is low-impact. But worth a brief comment noting the known concurrent-access behavior to help future readers.
No description provided.