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
- Set generationJobId to null/undefined when sourceType is 'manual'.
- If the observation really came from a job, use the job's actual source_type instead of 'manual'.
- 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
- Only attach generationJobId for source types produced by jobs (agent_event, session_summary, observation_reindex).
- Sanitize input objects once at the pipeline boundary instead of at each call site.
- Test each sourceType against addSource with and without generationJobId.
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
- generation_job_id must belong to project_id and team_id
- agent_event_id must belong to project_id and team_id
- agent_event source_id must belong to project_id and team_id
- agent_event source_id must equal agent_event_id
- audit log insert failed
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)