thedotmack/claude-mem · error · Error
SyncApply: invalid op seq=
Error message
SyncApply: invalid op seq=${op.seq} kind=${op.kind} origin=${op.origin_device}/${op.origin_id}: remap_project fields must set project and/or merged_into_project What it means
Even when fields is a valid object, remap_project must actually assign at least one of the two supported columns: project or merged_into_project. fieldString returns null for both when the keys are missing/non-string, leaving setClauses empty — the code then throws rather than issuing a no-op UPDATE.
Solutions
- Set fields.project and/or fields.merged_into_project to non-null string values
- Update producers using old/renamed field names to the supported keys
- If no remap is needed, do not enqueue a remap_project op at all
Example fix
// before
fields: { new_project: 'proj-b' }
// after
fields: { project: 'proj-b' } Defensive patterns
Strategy: validation
Validate before calling
function remapSetsSomething(fields: Record<string, unknown>): boolean {
return typeof fields.project === 'string' || typeof fields.merged_into_project === 'string';
}
if (!remapSetsSomething(op.body.fields)) throw new Error('remap must set project or merged_into_project'); Type guard
const hasRemapField = (f: unknown): f is { project?: string; merged_into_project?: string } =>
typeof f === 'object' && f !== null &&
('project' in f || 'merged_into_project' in f); Try / catch
try { apply(op); } catch (e) {
if (e instanceof InvalidOpError && e.reason.includes('must set project and/or merged_into_project')) {
log.warn(`no-op remap op seq=${e.seq} discarded`);
} else throw e;
} Prevention
- Use shared field-name constants between producer and consumer
- Skip enqueuing a remap op when no column actually changes
- Add a producer-side assertion before append
- Replace null coalescing on the producer with explicit identifiers
When it happens
Trigger: A remap_project op whose fields object contains only unrecognized keys (e.g. { name: 'x' }), or sets both project and merged_into_project to null/non-string values.
Common situations: Producer renamed fields after a schema change (e.g. new_project instead of project); op intended to clear values sent with null instead of valid identifiers; copy-pasted op template with placeholder fields.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- SyncApply: invalid op seq=
- canonical content
- device_id must be 1-128 characters
- deviceId must be non-empty
- name must be 1-80 characters
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/8dd3cbe880f7d935.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/SyncApply.ts:1101
const fields = body.fields as Record<string, unknown> | undefined;
if (!where || typeof where !== 'object' || !fields || typeof fields !== 'object') {
throw invalidOp(op, 'remap_project requires where and fields objects');
}
const setClauses: string[] = [];
const setParams: (string | number)[] = [];
const newProject = fieldString(op, fields, 'project');
const mergedInto = fieldString(op, fields, 'merged_into_project');
if (newProject !== null) {
setClauses.push('project = ?');
setParams.push(newProject);
}
if (mergedInto !== null) {
setClauses.push('merged_into_project = ?');
setParams.push(mergedInto);
}
if (setClauses.length === 0) {
throw invalidOp(op, 'remap_project fields must set project and/or merged_into_project');
}
const whereClauses: string[] = [];
const whereParams: (string | number)[] = [];
const whereProject = fieldString(op, where, 'project');
const whereMemorySessionId = fieldString(op, where, 'memory_session_id');
if (whereProject !== null) {
whereClauses.push('project = ?');
whereParams.push(whereProject);
}
if (whereMemorySessionId !== null) {
whereClauses.push('memory_session_id = ?');
whereParams.push(whereMemorySessionId);
}
if (where.merged_into_project_is_null === true) {
whereClauses.push('merged_into_project IS NULL');
}
if (whereClauses.length === 0) {View on GitHub (pinned to d8bc9755e7)