Environment
- opencode-mem: 2.26.0 (installed as opencode plugin,
opencode-mem@latest)
- OS: Windows 11
- AI provider:
openai-chat via OpenRouter (deepseek/deepseek-v4-flash-0731)
- Web UI: http://localhost:4747
Summary
The "Memory Tag Migration" modal detects memories with empty tags and offers to migrate them. Running the migration reports success, but the same memories remain untagged, so the modal reappears forever.
Root cause: in handleRunTagMigrationBatch (dist/services/api-handlers.js), when the AI call fails, the item is silently skipped — no tags written, no error recorded — but migrationProgress.processed++ still runs, so the batch is reported as complete.
Failure path
In handleRunTagMigrationBatch:
if (result.success && result.data?.tags) {
currentTags = result.data.tags;
await db.run("UPDATE memories SET tags = ? WHERE id = ?", [...]);
}
// if result.success === false → nothing happens here
...
const vector = await embeddingService.embedWithTimeout(m.content, ...);
await tursoVectorSearch.updateVector(db, m.id, vector, tagsVector);
migrationProgress.processed++; // ← counted as processed even though tags were never written
The catch block only logs when an exception is thrown. A { success: false } provider result (no throw) hits none of the error handling: no migrationProgress.errors.push(...), no log("Migration error for memory", ...). The loop just continues and the item counts as done.
Why the AI call fails (verified)
The configured model intermittently returns an empty tool-call payload over OpenRouter. Reproduced 1/3 times with a direct API call: choices[0].message.tool_calls[0].function.arguments === "{}". The provider then returns { success: false } (validation/model path), which the batch handler silently swallows as described above.
Auto-capture is affected by the same empty-arguments behavior (memories get persisted with tags = NULL), which is what keeps feeding new untagged memories into the migration detector.
Observed on my instance: 4 memories from the same evening all stuck with tags = NULL; migration run produced zero errors in the log and reported completion; GET /api/migration/tags/detect still returned count: 4 afterwards.
Suggested fix
- In the batch loop, treat
result.success === false as a failure: push to migrationProgress.errors, log it, and do NOT increment processed (or track a separate failed counter).
- Surface failures in the UI progress modal instead of reporting success.
- Optionally: retry failed items, and/or make
handleDetectTagMigration ignore memories that have already been attempted N times.
- Related: when the model returns empty tool arguments (
{}), log a warning — currently this failure mode is completely silent on the auto-capture path too.
Happy to provide more logs/details if needed.
Environment
opencode-mem@latest)openai-chatvia OpenRouter (deepseek/deepseek-v4-flash-0731)Summary
The "Memory Tag Migration" modal detects memories with empty
tagsand offers to migrate them. Running the migration reports success, but the same memories remain untagged, so the modal reappears forever.Root cause: in
handleRunTagMigrationBatch(dist/services/api-handlers.js), when the AI call fails, the item is silently skipped — no tags written, no error recorded — butmigrationProgress.processed++still runs, so the batch is reported as complete.Failure path
In
handleRunTagMigrationBatch:The catch block only logs when an exception is thrown. A
{ success: false }provider result (no throw) hits none of the error handling: nomigrationProgress.errors.push(...), nolog("Migration error for memory", ...). The loop just continues and the item counts as done.Why the AI call fails (verified)
The configured model intermittently returns an empty tool-call payload over OpenRouter. Reproduced 1/3 times with a direct API call:
choices[0].message.tool_calls[0].function.arguments === "{}". The provider then returns{ success: false }(validation/model path), which the batch handler silently swallows as described above.Auto-capture is affected by the same empty-arguments behavior (memories get persisted with
tags = NULL), which is what keeps feeding new untagged memories into the migration detector.Observed on my instance: 4 memories from the same evening all stuck with
tags = NULL; migration run produced zero errors in the log and reported completion;GET /api/migration/tags/detectstill returnedcount: 4afterwards.Suggested fix
result.success === falseas a failure: push tomigrationProgress.errors, log it, and do NOT incrementprocessed(or track a separatefailedcounter).handleDetectTagMigrationignore memories that have already been attempted N times.{}), log a warning — currently this failure mode is completely silent on the auto-capture path too.Happy to provide more logs/details if needed.