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

  1. Retry the save as the message suggests — `nextTime` will pick a new key on the attempt.
  2. Load existing trace metadata before saving so `nextTime` sees prior `savedAt` values.
  3. 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

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


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)