Cherry-pick tasks functionality from fotolog #8
Labels
No labels
agent
blocked
agent
new
agent
review
agent
working
complexity
high
complexity
low
priority
high
priority
low
priority
medium
risk
high
risk
low
risk
medium
type
bug
type
chore
type
feature
type
security
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
tfks/trippin#8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
The fotolog implementation has some nice things we'll want to borrow:
/needs-spec
Brief — Developer
Phase: fix. Run
fjx dev prompt applyfor the cycle protocol.Tasks
Fix TOCTOU crash in
enqueueTask(src/lib/worker/queue.ts:89–93)The fallback SELECT after a conflicting INSERT uses a non-null assertion (
existing.rows[0]![0]) that throws aTypeErrorif the conflicting task completes (transitions todone/failed) between the INSERT and the SELECT, because the partial unique index no longer covers it and the SELECT returns 0 rows.Replace the two-step INSERT + fallback SELECT with a single atomic CTE that inserts and falls back in one round-trip, eliminating the race window. Example shape:
Add a test that verifies the fallback path returns a row (use a pre-inserted
donetask with the same key to simulate the post-race state).Archive the openspec change — run the archive command for the change that covers issue #8 once the fix is committed and the PR is updated.
Ledger for: agent-pm
Status: closed @ 2026-05-23T01:58:45.635Z
Briefs:
Activity
(PM manages this ledger; agent section unused)
Ledger for: agent-dev
Status: review @ 2026-05-23T01:53:42.306Z
Fix cycle — TOCTOU crash in enqueueTask
Tasks
enqueueTaskwith atomic CTE6c4ba5e04a0436(already committed)Implementation notes
WITH ins AS (INSERT ... ON CONFLICT DO NOTHING RETURNING id) SELECT id FROM ins UNION ALL SELECT id FROM tasks WHERE key = $3 LIMIT 1. The entire statement runs at one snapshot, eliminating the race window."enqueueTask: fallback CTE returns a row when active task exists at conflict time"— pre-inserts a done task with the same key to confirm no crash and a valid id is returned in the post-race state.just validateis clean.Prior QA findings disposition
@erik's directive — address only the TOCTOU issue./spec-approved
Implementation looks good. @agent-dev needs to archive the spec and push changes before I'll accept the PR.
Brief — QA
Phase: review. Run
fjx qa prompt defaultfor the cycle protocol.Ledger for: agent-qa
Status: concerns @ 2026-05-22T23:33:08.313Z
Action summary
validate.yaml— success (run #37, commit04a0436)src/lib/worker/queue.ts88% lines / 92.9% branches;src/lib/worker/tasks.ts71.1% lines / 84.2% branches;src/lib/worker/worker-id.ts100%Gap analysis
1. TOCTOU crash in
enqueueTask— not exercised by any testqueue.ts:89–93executes a two-step INSERT-then-SELECT:Between step 1 and step 2, the conflicting task can complete (transition to
doneorfailed). When it does, the partial unique index no longer covers it, so step 2 returns 0 rows.existing.rows[0]isundefinedand the!assertion causes:The test suite cannot cover this because every dedup test runs inside
withTransaction, which holds the conflicting row in an uncommitted state for the duration of the test — the race window never opens. In production,enqueueTaskis called on autocommit connections, so the window is real.Severity: high — uncaught exception in the main write path, triggered by a race condition that becomes more likely under load.
Minimal fix: replace the hard assertion with a null check and either return the conflict-id via a different query (e.g. a CTE that does INSERT + fallback SELECT atomically) or define a fallback contract (re-enqueue as a fresh row if the conflicting task already finished).
2.
restartTaskre-enters the dedup window untestedrestartTasksetsstatus = 'pending'on a previously failed/done task. Thekeycolumn is not changed. This means the restarted row is back inside the partial unique index window, and the nextenqueueTaskwith the same type+payload will conflict and return the restarted task's id rather than a new row. This may be correct but is completely untested. If the intent is the opposite — restarted tasks should not block new enqueues — this is a silent functional bug.3. Admin UI does not surface new columns
getTaskandlistTasksnow returnkey,worker, andresults. The admin task detail view (src/routes/admin/tasks/[id].tsx) was not updated — data is fetched and silently discarded. The new columns are invisible to operators.4. 64-bit hash key space (informational)
deriveTaskKeytruncates SHA-256 to 16 hex chars (8 bytes / 64 bits). Birthday collision becomes ~1% at ~400 million tasks — vanishingly unlikely for this app. Noted as an undocumented assumption.Confirmed issues
src/lib/worker/queue.ts:93existing.rows[0]![0]throws TypeError when conflicting task completes before the fallback SELECTsrc/lib/worker/queue.ts+restartTasksrc/routes/admin/tasks/[id].tsxkey,worker,resultssilently discarded — not rendered in the task detail viewUntested assumptions
withTransaction, which prevents the conflict fallback path (queue.ts:89–93) from ever executing — 0 test coverage for the behaviorally critical branch.Verdict
concerns — the TOCTOU crash (finding 1) is a real defect in the production write path. Under concurrency it will surface as an uncaught exception. The fix is small but must happen before merge. Findings 2 and 3 should be addressed or explicitly accepted.
Dev tasks (original)
b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54b5f0e54Notes
Address the TOCTOU issue and ignore the other issues by agent-qa. @agent-dev, when done, archive the openspec change.