fix(tasks): defensive user fallback in handleCancel persist (#1108) #1110
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!1110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "code-lead/1108"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #1108.
Summary
handleCancelpopulatestask_history.userfrom the in-memory record'suser, then falls back through the running task'sgit_authorto a sentinel (<unknown>) so theNOT NULLinvariant ontask_history.useris always honoured.persistTaskcall sites — the queued-drop and the running-cancel — route through a newpersistCancelOrLoghelper that wraps the synchronous SQLite call in try/catch so any residual failure (DB locked, schema drift, etc.) logs a structured[cancel]line instead of escaping asunhandledRejection.request.git_author; the in-memoryTaskRecord.userwasundefined, drizzle serialised it as NULL, and SQLite rejected the insert withSQLITE_CONSTRAINT_NOTNULL.Test plan
bun test apps/server/src/main.test.ts -t "POST /cancel"— all 16 cancel-route tests pass (4 new + 12 existing).task_history persistence (#1108):user.record.userfalls back torequest.git_author; nounhandledRejectionfires.CANCEL_UNKNOWN_USER.user.bun test apps/server/src/main.test.ts(74 pass).just typecheckclean.just lint/just fmt-checkclean on touched files.Related (out of scope)
#task-history-on-startlands, the row will already exist by cancel time and this handler can switch to UPDATE instead of INSERT — at which point the sentinel fallback can be removed.apps/server/src/domain/dispatch/cancel.ts::persistAndBroadcastCancellation) carries the identicalusershape and has the same latent vulnerability via theissues.unassignedwebhook path; a follow-up should fold that helper through the same defensive pattern. Out of scope here per the AC.🤖 Generated with Claude Code
LGTM. Clean, targeted fix for the
SQLITE_CONSTRAINT_NOTNULLcrash ontask_history.user.Correctness
persistCancelOrLogcorrectly usestry/catch(not.catch()) sincepersistTaskis a synchronousbun:sqlitecall. The wrapper mirrors the existing non-awaited call pattern and logs residual failures rather than letting them surface asunhandledRejection.record.user || request.git_author || CANCEL_UNKNOWN_USERis sound:||catches bothundefinedand"", which correctly handles the cursor-CLI dispatch path wheregit_authorwas never populated. The<unknown>sentinel satisfies theNOT NULLinvariant as a last resort.Tests
All four cases are covered:
record.userregression — falls through togit_author;unhandledRejectionlistener verifies the crash path is gone.task_history.user.CI: ✅ green
No concerns.