thedotmack/claude-mem · error · Error

manual observation sources cannot be linked to a…

Error message

manual observation sources cannot be linked to a generation_job_id

What it means

When a generationJobId is supplied, assertGenerationJobMatchesSource rejects sourceType 'manual' outright: a manual observation by definition was not produced by a generation job, so linking one is an invalid state combination.

Solutions

  1. Set generationJobId to null/undefined when sourceType is 'manual'.
  2. If the observation really came from a job, use the job's actual source_type instead of 'manual'.
  3. Strip generationJobId in the caller before dispatching manual sources to addSource.

Example fix

// before
await repo.addSource({ sourceType: 'manual', generationJobId: jobId, ... });
// after
await repo.addSource({ sourceType: 'manual', generationJobId: null, ... });
Defensive patterns

Strategy: validation

Validate before calling

if (sourceType === 'manual' && generationJobId) {
  throw new Error('strip generationJobId for manual sources before addSource');
}

Type guard

function canLinkGenerationJob(input: { sourceType: string; generationJobId?: string | null }): input is { sourceType: string; generationJobId: string } {
  return !!input.generationJobId && input.sourceType !== 'manual';
}

Try / catch

try {
  await repo.addSource(input);
} catch (e) {
  if (e instanceof Error && e.message.includes('cannot be linked to a generation_job_id')) {
    await repo.addSource({ ...input, generationJobId: null }); // retry without job link
  } else throw e;
}

Prevention

When it happens

Trigger: Calling addSource with sourceType='manual' and a non-null generationJobId, e.g. a generic code path that always forwards generationJobId regardless of source type.

Common situations: Bulk importers that stamp generationJobId on every source; UI flows that create manual notes while a generation job context is active; tests reusing an input object across source types.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/e0092278dd7763c6. Report an issue: GitHub.

Appendix: source

Thrown at src/storage/postgres/observations.ts:332

  );
  if (!row) {
    throw new Error('generation_job_id must belong to project_id and team_id');
  }
}

async function assertGenerationJobMatchesSource(
  client: PostgresQueryable,
  input: {
    generationJobId: string;
    projectId: string;
    teamId: string;
    sourceType: ObservationSourceType;
    sourceId: string;
    agentEventId: string | null;
  }
): Promise<void> {
  if (input.sourceType === 'manual') {
    throw new Error('manual observation sources cannot be linked to a generation_job_id');
  }

  const row = await queryOne<{
    id: string;
    source_type: string;
    source_id: string;
    agent_event_id: string | null;
  }>(
    client,
    `
      SELECT id, source_type, source_id, agent_event_id
      FROM observation_generation_jobs
      WHERE id = $1 AND project_id = $2 AND team_id = $3
    `,
    [input.generationJobId, input.projectId, input.teamId]
  );
  if (!row) {
    throw new Error('generation_job_id must belong to project_id and team_id');

View on GitHub (pinned to d8bc9755e7)