fix(tasks): handleCancel crashes with NOT NULL constraint failed: task_history.user #1108

Closed
opened 2026-05-11 16:34:10 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As an operator using POST /api/cancel to kill a stuck task, I want the cancel to succeed and persist a cancelled row in task_history, so that the cancel attempt isn't swallowed by an uncaught SQLite exception.

Repro

2026-05-11 ~15:26 UTC:

[cursor-cli-adapter] aborting cursor-agent pid=2740455
SQLiteError: NOT NULL constraint failed: task_history.user
    errno: 1299, code: "SQLITE_CONSTRAINT_NOTNULL"
  at #run (bun:sqlite:185:20)
  at persistTask (apps/server/src/infrastructure/database/task-store.ts:206:108)
  at handleCancel (apps/server/src/main.ts:1285:3)
  at async dispatch (hono compose.js:22:23)
[dev] 1b8e7ea2-4576-441f-8e64-28d1c95722e0: done — task completed
[process] unhandledRejection: SQLiteError: NOT NULL constraint failed: task_history.user

The cancel succeeded in killing the cursor-agent process, but the post-cancel persistTask insert failed because the cancel handler didn't populate task_history.user.

Root cause

handleCancel (apps/server/src/main.ts:1285) builds a task_history payload without a user field. The task_history schema declares user TEXT NOT NULL (see PRAGMA table_info(task_history)). Insert fails with constraint error 1299.

Acceptance criteria

Fix

  • handleCancel populates task_history.user from the in-memory running task record (the user field set at dispatch time).
  • If the running task record is missing the user (defensive fallback), use an explicit string like "<unknown>" rather than NULL — keep the NOT NULL invariant.
  • Wrap the persist call in try/catch and log a structured error if it still fails — don't crash with unhandledRejection.

Tests

  • Unit test: dispatch a task, cancel via POST /api/cancel, assert task_history row exists with status='cancelled' + correct user.
  • Regression test: assert no unhandledRejection fires from the cancel path.
  • Once #task-history-on-start lands, the row will already exist by cancel time → the cancel handler should UPDATE not INSERT; this fix becomes simpler.

References

  • Stack trace from systemd journal 2026-05-11 15:26:31 UTC.
  • Code: apps/server/src/main.ts:1285 (handleCancel) + apps/server/src/infrastructure/database/task-store.ts:206 (persistTask).
  • Companion bugs: #stale-session-resume, #task-history-on-start.
## User story As an operator using `POST /api/cancel` to kill a stuck task, I want the cancel to succeed and persist a `cancelled` row in `task_history`, so that the cancel attempt isn't swallowed by an uncaught SQLite exception. ## Repro 2026-05-11 ~15:26 UTC: ``` [cursor-cli-adapter] aborting cursor-agent pid=2740455 SQLiteError: NOT NULL constraint failed: task_history.user errno: 1299, code: "SQLITE_CONSTRAINT_NOTNULL" at #run (bun:sqlite:185:20) at persistTask (apps/server/src/infrastructure/database/task-store.ts:206:108) at handleCancel (apps/server/src/main.ts:1285:3) at async dispatch (hono compose.js:22:23) [dev] 1b8e7ea2-4576-441f-8e64-28d1c95722e0: done — task completed [process] unhandledRejection: SQLiteError: NOT NULL constraint failed: task_history.user ``` The cancel succeeded in killing the cursor-agent process, but the post-cancel `persistTask` insert failed because the cancel handler didn't populate `task_history.user`. ## Root cause `handleCancel` (`apps/server/src/main.ts:1285`) builds a task_history payload without a `user` field. The `task_history` schema declares `user TEXT NOT NULL` (see `PRAGMA table_info(task_history)`). Insert fails with constraint error 1299. ## Acceptance criteria ### Fix - [ ] `handleCancel` populates `task_history.user` from the in-memory running task record (the `user` field set at dispatch time). - [ ] If the running task record is missing the user (defensive fallback), use an explicit string like `"<unknown>"` rather than NULL — keep the NOT NULL invariant. - [ ] Wrap the persist call in try/catch and log a structured error if it still fails — don't crash with `unhandledRejection`. ### Tests - [ ] Unit test: dispatch a task, cancel via `POST /api/cancel`, assert `task_history` row exists with `status='cancelled'` + correct `user`. - [ ] Regression test: assert no `unhandledRejection` fires from the cancel path. ### Related (out of scope but worth noting in PR) - Once #task-history-on-start lands, the row will already exist by cancel time → the cancel handler should UPDATE not INSERT; this fix becomes simpler. ## References - Stack trace from systemd journal 2026-05-11 15:26:31 UTC. - Code: `apps/server/src/main.ts:1285` (handleCancel) + `apps/server/src/infrastructure/database/task-store.ts:206` (persistTask). - Companion bugs: #stale-session-resume, #task-history-on-start.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#1108
No description provided.