TencentCloud/TencentDB-Agent-Memory · error

Source not found: ${sourceKey}

Error message

Source not found: ${sourceKey}

What it means

ScopedStorageAdapter.rename implements rename as a non-atomic get → put → delete. It throws this error when the source object does not exist in the backend. The code comments note a native renameObject on IStorageBackend is required for true atomicity (tracked long-term, audit H-6).

Source

Thrown at MemoryCore/src/core/storage/adapter.ts:216

    return {
      key,
      size: obj.size ?? obj.content.length,
      lastModified,
      createdAt: lastModified,
    };
  }

  // ── fs.rename replacement ──

  async rename(sourceKey: string, destKey: string): Promise<void> {
    // CR-8 partial fix (2026-05-19): preserve contentType + metadata across rename.
    // The 3-step (get → put → delete) is still NOT atomic; if the process is killed
    // between put and delete, both source and dest will exist (data duplication).
    // A complete fix requires a native renameObject in IStorageBackend (using
    // POSIX fs.rename for local + COS x-cos-copy-source for remote). Tracked as
    // long-term work — see audit report H-6 (persona.md backup rotation).
    const obj = await this.backend.getObject(sourceKey);
    if (!obj) throw new Error(`Source not found: ${sourceKey}`);
    await this.backend.putObject(destKey, obj.content, {
      contentType: obj.contentType,
      metadata: obj.metadata,
    });
    await this.backend.deleteObject(sourceKey);
  }

  // ── fs.copyFile replacement ──

  async copyFile(sourceKey: string, destKey: string): Promise<void> {
    // CR-8 partial fix (2026-05-19): preserve contentType + metadata across copy.
    const obj = await this.backend.getObject(sourceKey);
    if (!obj) throw new Error(`Source not found: ${sourceKey}`);
    await this.backend.putObject(destKey, obj.content, {
      contentType: obj.contentType,
      metadata: obj.metadata,
    });
  }

View on GitHub (pinned to 3efcd317b8)

Solutions

  1. Ensure the source object exists before renaming (write it first or check exists())
  2. If the intent is 'create or replace', use putObject on destKey instead of rename
  3. Guard with exists() and skip or create the source as appropriate
  4. Serialize concurrent operations touching the same key to avoid a delete/rename race

Example fix

// before
await adapter.rename(tmpKey, finalKey); // throws if tmpKey missing
// after
if (await adapter.exists(tmpKey)) await adapter.rename(tmpKey, finalKey);
else await adapter.putObject(finalKey, defaultContent);
Defensive patterns

Strategy: validation

Validate before calling

if (!(await adapter.exists(sourceKey))) {
  throw new NotFoundError(`cannot rename missing object: ${sourceKey}`);
}
await adapter.rename(sourceKey, destKey);

Try / catch

try {
  await adapter.rename(src, dst);
} catch (e) {
  if (String(e.message).startsWith('Source not found:')) {
    await adapter.putObject(dst, defaultContent); // create-or-replace semantics
  } else throw e;
}

Prevention

When it happens

Trigger: Calling rename(sourceKey, destKey) — directly or indirectly via atomicWriteJson — when getObject(sourceKey) returns null, i.e. the source object was never written or was already deleted.

Common situations: atomicWriteJson on a path whose current file doesn't exist yet when expecting one; racing deletes from another worker; key mismatch (case, prefix, extension) between write and rename.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of TencentCloud/TencentDB-Agent-Memory@3efcd317b8 (2026-09-01). Data as JSON: /api/errors/a5c9078504931561. Report an issue: GitHub.