You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[BUG] bulk-create non-deterministically fails tasks with "Internal Server Error": concurrent writes hit SQLite database is locked, and 5xx is never retried #79
vikunja_tasks bulk-create randomly fails a large fraction of its tasks with "Internal Server Error". In my repro, a 12-task bulk-create returned success: false with 6 created / 6 failed, and which indexes fail is non-deterministic run to run. It is not payload-related: a task whose description was one character failed while 100+-char tasks in the same batch succeeded, and the raw Vikunja API accepts every one of the same payloads when sent sequentially (12/12 created).
Root cause
src/tools/tasks/bulk-operations-simplified.ts:22 runs creates through a BatchProcessor with maxConcurrency: 8. Vikunja instances on the default SQLite backend serialize writes; 8 concurrent PUT /projects/{id}/tasks requests exceed the busy timeout and the server returns HTTP 500 database is locked for the losers:
level=ERROR method=PUT uri=/api/v1/projects/17/tasks status=500 err="database is locked" (x6)
This reproduces with no MCP involved — 8 parallel raw-API creates vs 8 sequential:
$ foriin 1..8;do curl -X PUT .../projects/17/tasks -d "{\"title\":\"probe $i\"}"&done;waitreq2: 500 # database is lockedreq1..8 (rest): 201
$ foriin 1..8;do curl -X PUT .../projects/17/tasks -d "{\"title\":\"probe $i\"}";doneseq1..8: 201 # sequential: all succeed
The withRetry wrapper in the bulk path only retries authentication errors (shouldRetry: isAuthenticationError), so a 500 database is locked — which is transient by definition — is never retried and surfaces as a permanent per-task failure.
To Reproduce
Run Vikunja 2.3.0 with the SQLite backend (the default / official Docker image).
{
"success": false,
"message": "Bulk create partially completed. Successfully created 6 tasks, 6 failed.",
"metadata": {
"count": 6, "failedCount": 6,
"failures": [
{"index": 0, "error": "Internal Server Error"},
{"index": 3, "error": "Internal Server Error"},
{"index": 5, "error": "Internal Server Error"},
{"index": 6, "error": "Internal Server Error"},
{"index": 9, "error": "Internal Server Error"},
{"index": 11, "error": "Internal Server Error"}
]
}
}
Expected behavior
All 12 tasks created. Transient 5xx/lock errors should be retried with backoff (or writes throttled to what the backend can take); a batch that the raw API handles fine sequentially should not half-fail through the MCP.
Wider impact — bulk-update silently corrupts through the same mechanism
bulkUpdateTasks' individual-update fallback uses the same concurrent pattern (maxConcurrency: 5). In my 3-task bulk-update repro, 2 of 3 fallback PUTs died on database is locked, the failure was demoted to a logger.warn, and the tool reported success: true, "Successfully updated 3 tasks" while only 1 task was actually updated (details in my comment on #46). So the same root cause produces silent partial failure there, not even a success: false. PR #63's per-task-merge rewrite keeps maxConcurrency: 5 and would inherit this.
Suggested fix
Retry per-item on HTTP 5xx / database is locked with exponential backoff (extend shouldRetry beyond auth errors), and/or
Describe the bug
vikunja_tasks bulk-createrandomly fails a large fraction of its tasks with"Internal Server Error". In my repro, a 12-task bulk-create returnedsuccess: falsewith 6 created / 6 failed, and which indexes fail is non-deterministic run to run. It is not payload-related: a task whose description was one character failed while 100+-char tasks in the same batch succeeded, and the raw Vikunja API accepts every one of the same payloads when sent sequentially (12/12 created).Root cause
src/tools/tasks/bulk-operations-simplified.ts:22runs creates through aBatchProcessorwithmaxConcurrency: 8. Vikunja instances on the default SQLite backend serialize writes; 8 concurrentPUT /projects/{id}/tasksrequests exceed the busy timeout and the server returns HTTP 500database is lockedfor the losers:This reproduces with no MCP involved — 8 parallel raw-API creates vs 8 sequential:
The
withRetrywrapper in the bulk path only retries authentication errors (shouldRetry: isAuthenticationError), so a 500database is locked— which is transient by definition — is never retried and surfaces as a permanent per-task failure.To Reproduce
vikunja_tasks { subcommand: "bulk-create", projectId: <id>, tasks: [<12 tasks with titles/descriptions/priorities>] }.{ "success": false, "message": "Bulk create partially completed. Successfully created 6 tasks, 6 failed.", "metadata": { "count": 6, "failedCount": 6, "failures": [ {"index": 0, "error": "Internal Server Error"}, {"index": 3, "error": "Internal Server Error"}, {"index": 5, "error": "Internal Server Error"}, {"index": 6, "error": "Internal Server Error"}, {"index": 9, "error": "Internal Server Error"}, {"index": 11, "error": "Internal Server Error"} ] } }Expected behavior
All 12 tasks created. Transient 5xx/lock errors should be retried with backoff (or writes throttled to what the backend can take); a batch that the raw API handles fine sequentially should not half-fail through the MCP.
Wider impact — bulk-update silently corrupts through the same mechanism
bulkUpdateTasks' individual-update fallback uses the same concurrent pattern (maxConcurrency: 5). In my 3-task bulk-update repro, 2 of 3 fallback PUTs died ondatabase is locked, the failure was demoted to alogger.warn, and the tool reportedsuccess: true, "Successfully updated 3 tasks"while only 1 task was actually updated (details in my comment on #46). So the same root cause produces silent partial failure there, not even asuccess: false. PR #63's per-task-merge rewrite keepsmaxConcurrency: 5and would inherit this.Suggested fix
database is lockedwith exponential backoff (extendshouldRetrybeyond auth errors), and/orsuccess: truewhen any item in the batch failed.Environment
@democratize-technology/vikunja-mcp0.2.0 (vianpx -y; code path unchanged atmaina42e1c2)