thedotmack/claude-mem · warning

generation job ${job.id} not found in scope

Error message

generation job ${job.id} not found in scope

What it means

Twin of the FOLDER_MD_EXCLUDE parse failure: the #2400 skeleton deny-list setting CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST must be a JSON array of glob strings ('[]' default). JSON.parse threw and the code warns, leaving skeletonDenylistPatterns empty — meaning empty/skeleton CLAUDE.md files will be injected in folders the user meant to suppress (the deny-list only suppresses injection when generated content is empty; active folders still get their CLAUDE.md).

Source

Thrown at src/server/generation/processGeneratedResponse.ts:254

): Promise<ProcessGeneratedResponseOutcome> {
  const { job } = input;

  return withPostgresTransaction(input.pool, async (client) => {
    const obsRepo = new PostgresObservationRepository(client);
    const sourcesRepo = new PostgresObservationSourcesRepository(client);
    const jobsRepo = new PostgresObservationGenerationJobRepository(client);
    const eventsLogRepo = new PostgresObservationGenerationJobEventsRepository(client);
    const auditRepo = new PostgresAuthRepository(client);

    // Reload the job inside the transaction. If it was already completed
    // by another worker, return its existing observations idempotently.
    const fresh = await jobsRepo.getByIdForScope({
      id: job.id,
      projectId: job.projectId,
      teamId: job.teamId,
    });
    if (!fresh) {
      throw new Error(`generation job ${job.id} not found in scope`);
    }
    if (fresh.status === 'completed' || fresh.status === 'cancelled' || fresh.status === 'failed') {
      logger.info('SYSTEM', 'generation job already in terminal status; skipping persistence', {
        jobId: fresh.id,
        status: fresh.status,
      });
      return {
        kind: 'completed' as const,
        jobId: fresh.id,
        observations: [],
        privateContentDetected,
      };
    }

    const persisted: PostgresObservation[] = [];
    for (let index = 0; index < rendered.length; index++) {
      const { kind, content, metadata } = rendered[index]!;
      if (!content || content.trim().length === 0) {

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Set the value as a JSON array: CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST=["packages/fixtures","examples"]
  2. Round-trip check: `node -e 'JSON.parse(process.argv[1])' "$CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST"`.
  3. Remove the variable entirely if you want the default empty deny-list.
  4. Check the sibling CLAUDE_MEM_FOLDER_MD_EXCLUDE too — the same edit mistake usually breaks both.

Example fix

# before
CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST=packages/fixtures,examples

# after
CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST=["packages/fixtures","examples"]
Defensive patterns

Strategy: validation

Validate before calling

function parseJsonStringArray(raw: string | undefined, name: string): string[] {
  if (!raw) return [];
  try {
    const parsed: unknown = JSON.parse(raw);
    if (isStringArray(parsed)) return parsed;
  } catch { /* fall through */ }
  throw new Error(`${name} must be a JSON array of strings, got: ${raw}`);
}

const denylist = parseJsonStringArray(settings.CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST, 'CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST');

Type guard

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(v => typeof v === 'string');
}

Prevention

When it happens

Trigger: Setting the deny-list as comma-separated text instead of a JSON array; quoting mistakes in shell profiles or CI variables; smart-quote copy-paste from rendered docs.

Common situations: Hardcoding per-machine overrides after reading issue #2400; monorepo configs where the env var passes through several layers of quoting.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/c15a80345b09c5ff. Report an issue: GitHub.