Skip to content

[BUG] bulk-create non-deterministically fails tasks with "Internal Server Error": concurrent writes hit SQLite database is locked, and 5xx is never retried #79

Description

@angusmaul

Describe the bug

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:

$ for i in 1..8; do curl -X PUT .../projects/17/tasks -d "{\"title\":\"probe $i\"}" & done; wait
req2: 500        # database is locked
req1..8 (rest): 201

$ for i in 1..8; do curl -X PUT .../projects/17/tasks -d "{\"title\":\"probe $i\"}"; done
seq1..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

  1. Run Vikunja 2.3.0 with the SQLite backend (the default / official Docker image).
  2. Call vikunja_tasks { subcommand: "bulk-create", projectId: <id>, tasks: [<12 tasks with titles/descriptions/priorities>] }.
  3. Observe a response like:
{
  "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
  • Drop default write concurrency to 1–2 (SQLite is Vikunja's default backend; issue Optimize bulk operations with concurrent processing using Promise.allSettled #4 introduced the concurrency as an optimization, but correctness should win), optionally configurable via env, and
  • Never report success: true when any item in the batch failed.

Environment

  • Package: @democratize-technology/vikunja-mcp 0.2.0 (via npx -y; code path unchanged at main a42e1c2)
  • Node.js: v24.15.0 (Windows 11 client)
  • Vikunja: 2.3.0 (official Docker image, SQLite backend, Linux host)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions