TencentCloud/TencentDB-Agent-Memory · error · SkillCoreError

SKILL_ID_COLLISION

SKILL_ID_COLLISION

Error message

failed to generate a unique skill_id after ${MAX_ID_ATTEMPTS} attempts

What it means

During create, SkillCore generates a skill id (CSPRNG base62 ulid, ~71 bits) and preflights it against the store via getHeadIncludingArchived, retrying up to MAX_ID_ATTEMPTS (3). If all attempts collide with an existing id — including archived heads — it throws SKILL_ID_COLLISION. Since random collision probability is ~1e-10 per attempt, three consecutive hits means the id generator is broken, not unlucky.

Source

Thrown at MemoryCore/src/core/skill/skill-core.ts:285

    // → 应用层 preflight 是唯一可移植到两种 store 的方案。
    //
    // 注:注入的 ulid 工厂可能不带 'skl-' 前缀,这里兜底拼上。
    const MAX_ID_ATTEMPTS = 3;
    let sid = "";
    for (let attempt = 1; attempt <= MAX_ID_ATTEMPTS; attempt++) {
      const u = this.ulid();
      sid = u.startsWith("skl-") ? u : `skl-${u}`;

      // 全 team 范围查(不带 team_id):只要 skill_id 全局撞了就重试。
      // 用 getHeadIncludingArchived 覆盖 archived 行——归档不代表 sid 空闲,
      // 版本表 UNIQUE(skill_id, version) 仍会挡住写入。
      const existing = await this.store.getHeadIncludingArchived(sid);
      if (!existing) break;

      if (attempt >= MAX_ID_ATTEMPTS) {
        // 连续 3 次撞 —— 只可能是 ulid 注入器坏了(比如测试里固定返回同一值)
        // 或熵源崩了,不是概率事件,直接抛。
        throw new SkillCoreError(
          "SKILL_ID_COLLISION",
          `failed to generate a unique skill_id after ${MAX_ID_ATTEMPTS} attempts`,
        );
      }
    }

    try {
      return await this.versioning.createNewSkill(
        sid,
        input.agent_id ?? "default",
        { user_id: input.user_id, team_id: input.team_id, agent_id: input.agent_id, task_id: input.task_id },
        {
          content: input.content,
          name: input.name,
          description: file.frontmatter.description,
          resourcesToWrite: input.resources,
          metadata_json: input.metadata ? JSON.stringify(input.metadata) : undefined,
        },

View on GitHub (pinned to 3efcd317b8)

Solutions

  1. Fix the ulid generator/injector so successive calls return fresh values (the code comments say 3 collisions means the injector is broken)
  2. In tests, make the fake generator return a sequence (e.g. incrementing values) instead of a fixed id
  3. Verify the entropy source (crypto.getRandomValues / crypto.randomBytes) is functioning in the runtime
  4. If collisions are genuinely from restored data, ensure archived skills share the same id namespace intentionally and adjust preflight or MAX_ID_ATTEMPTS

Example fix

// before: deterministic id in tests -> 3 collisions
const ids = ["AAAAAAAAAAAA"];
jest.spyOn(idgen, "generateId").mockImplementation(() => ids[0]);
// after: sequenced fake id
counter += 1;
jest.spyOn(idgen, "generateId").mockImplementation(() => `TESTID${String(counter).padStart(8, "0")}`);
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check the id generator before bulk creates
const samples = new Set(Array.from({ length: 100 }, () => generateId()));
if (samples.size < 100) throw new Error("ulid generator is producing duplicates");

Type guard

function idGeneratorHealthy(gen: () => string, n = 100): boolean {
  const s = new Set<string>();
  for (let i = 0; i < n; i++) s.add(gen());
  return s.size === n;
}

Try / catch

try {
  return await core.create(input);
} catch (e) {
  if (isSkillCoreError(e) && e.code === "SKILL_ID_COLLISION") {
    // do NOT blind-retry: the generator is likely broken
    logger.error("ulid injector appears deterministic/failing; aborting create");
    throw new Error("skill id generator unhealthy", { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: The ulid injector/entropy source is deterministic or failed — e.g. tests fixing the generator to return the same value, a seeded/frozen CSPRNG, or Math.random/Date-based id generation that repeats — so getHeadIncludingArchived(sid) returns an existing head on every attempt.

Common situations: Unit tests with a stubbed id generator returning a constant; an id function closed over a stale timestamp; entropy source failure in constrained environments; replaying a captured id stream that re-issues the same id.


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