Hmbown/CodeWhale · warning · Error
A saved trace identifier collided. Try saving again.
Error message
A saved trace identifier collided. Try saving again.
What it means
Thrown inside the IndexedDB `traces.get(key)` success callback when saving a new trace (`expected` is undefined) but an existing record already occupies the generated key. The key is time-derived (`nextTime` disambiguates only against loaded records), so a collision with an unseen record is possible; the save is aborted and the caller told to retry so a fresh key is generated.
Solutions
- Retry the save as the message suggests — `nextTime` will pick a new key on the attempt.
- Load existing trace metadata before saving so `nextTime` sees prior `savedAt` values.
- Add a random component or monotonic counter to key generation to make collisions practically impossible.
Example fix
// before const savedAt = this.nextTime(previous?.savedAt); // may still collide // after const savedAt = this.nextTime(previous?.savedAt); if (previous) return retryWithNewKey(); // regenerate key and re-run put
Defensive patterns
Strategy: retry
Validate before calling
const existing = await library.listTraceMetas(); // ensure nextTime is seeded with all known savedAt values before saving
Type guard
null
Try / catch
try { await library.save(key, trace); } catch (e) { if (e.message.includes('collided')) await library.save(newKey(), trace); } Prevention
- Load all existing trace metadata before generating new keys.
- Use one writer tab, or coordinate saves across tabs.
- Add a random suffix to generated keys to make collisions unlikely.
When it happens
Trigger: Calling `save` with no `expected` value while another record already exists with the same generated key — typically after importing/restoring records the in-memory time base does not know about, or two concurrent saves computing the same `savedAt`.
Common situations: Rapid consecutive saves, multiple tabs sharing the same IndexedDB, importing saved traces then saving new ones whose derived timestamps collide.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- This saved trace changed in another tab. Reopen it before…
- This saved trace was deleted in another tab. Use Save…
- a window change is already in progress
- Another pet owner is running
- Another pet recorder is using this output.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/f602195c199fea54.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/ui/storage.ts:83
const fail = (error: Error) => { failure ??= error; try { tx.abort(); } catch { reject(failure); } };
tx.oncomplete = () => failure ? reject(failure) : resolve(result);
tx.onerror = () => { failure ??= problem(tx.error); };
tx.onabort = () => reject(failure ?? problem(tx.error));
try { action(tx.objectStore('traces'), tx.objectStore('summaries'), value => { result = value; }, fail, tx.objectStore('habitats')); }
catch (e) { fail(e as Error); }
});
}
async save(trace: Trace, expected?: SavedReference): Promise<SavedReference> {
await this.open(); // Origin error before secure-context-only randomUUID.
const key = expected?.key ?? crypto.randomUUID();
return this.transaction('readwrite', (traces, metas, done, fail) => {
const req = traces.get(key);
req.onsuccess = () => {
try {
const previous: SavedTrace | undefined = req.result;
if (expected && !previous) throw missing();
if (expected && previous!.savedAt !== expected.savedAt) throw changed();
if (!expected && previous) throw new Error('A saved trace identifier collided. Try saving again.');
const savedAt = this.nextTime(previous?.savedAt), item: SavedTrace = { key, savedAt, trace };
traces.put(item); metas.put(summary(item)); done({ key, savedAt });
} catch (e) { fail(e as Error); }
};
});
}
private nextTime(previous?: string): string {
const last = previous ? Date.parse(previous) : 0;
return new Date(Math.max(Date.now(), Number.isFinite(last) ? last + 1 : 0)).toISOString();
}
async list(): Promise<SavedSummary[]> {
return this.transaction('readonly', (_traces, metas, done) => {
const req = metas.getAll();
req.onsuccess = () => done((req.result as SavedSummary[]).sort((a, b) => b.savedAt.localeCompare(a.savedAt)));
});
}
async getEntry(key: string): Promise<SavedTrace> {
return this.transaction('readonly', (traces, _metas, done, fail) => {View on GitHub (pinned to 433685b202)