feat(tasks): SessionStart reads the claim — a compaction gets its work back (milestone 381 step 3)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m35s
CI & Build / Build & push image (push) Canceled after 45s

The claim now pays rent to the session that set it. The SessionStart hook
sends the host's `source` and the session id; the server renders a claim
section by source (task_claims.render_claims):

- compact / clear: the work this session had claimed, each with its two
  latest log entries, so a compacted session resumes from the record
  rather than from a count of open tasks.
- startup / clear: other sessions' live claims (may still be running) and
  in-progress tasks whose claim went quiet (abandoned mid-task).
- fork: the same, framed as "two sessions may now hold this".
- resume, or no source sent: nothing.

The task sidebar shows a live claim as "Being worked" and a dead one on
open work as "Went quiet", so the operator sees a session die mid-task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 06:41:19 -04:00
co-authored by Claude Opus 5.5
parent a585fe0be0
commit e46eea3b52
8 changed files with 313 additions and 7 deletions
+12
View File
@@ -29,6 +29,10 @@ export interface Note {
due_date: string | null;
started_at: string | null;
completed_at: string | null;
// Which session is working this task now (milestone 381). Null when no
// session ever claimed it; `live` false when the claim's lease ran out —
// a session that went quiet mid-task, which is worth seeing, not hiding.
claim?: TaskClaim | null;
recurrence_rule: Record<string, unknown> | null;
recurrence_next_spawn_at: string | null;
is_task: boolean;
@@ -53,3 +57,11 @@ export interface NoteListResponse {
notes: Note[];
total: number;
}
export interface TaskClaim {
held_by: number | null;
session: string | null;
since: string | null;
touched: string | null;
live: boolean;
}