Skip to content

[0/8] Stop a task write from dropping metadata or deleting the file - #281

Open
alex-clickhouse wants to merge 1 commit into
mainfrom
alex-clickhouse/task-write-preserve
Open

[0/8] Stop a task write from dropping metadata or deleting the file#281
alex-clickhouse wants to merge 1 commit into
mainfrom
alex-clickhouse/task-write-preserve

Conversation

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

Base of the task-board stack. Two defects in the task write path, both of which
lose user data. They sit below #272 because both are present on main today and
neither depends on the board work.

The metadata columns

upsert_task replaced every column on every call. A caller that rewrote a row
for some other reason — a file move, an appended note — had to read the row
first and pass source, source_url, deadline and tags back, or the write
nulled them.

Two bugs came from a caller that did not:

The nulled source_url also broke duplicate detection, because
_find_duplicate_tasks matches that column first. A completed task stopped
matching the source item that created it, so the next sync of that item made a
second task.

Those four columns are now preserve-on-omit: an omitted column keeps its stored
value. None still clears one, and "" still clears tags. Five call sites
stop restoring what they never change, and reindex stops merging file values
against the stored row by hand.

The second completion

The move wrote the destination and unlinked the source. Both are one path when
a task is completed twice, because the first completion stores a file_path
under done/ and the second one reads that path back as its source. The sequence
wrote the file and then deleted it. The task kept its row and lost its whole
markdown file, including the update history it had collected.

Reaching it needs nothing unusual. An agent that retries task_done after a
timeout sends the call twice, and with #272 the board can move a card out of
Done and back in. move_task_file now appends and renames, and a rename onto
one path is a no-op.

Tests

  • test_task_upsert_preserve.py — the preserve-on-omit rule, its explicit
    clears, and the FTS text.
  • test_task_completion.py — three tests for the second completion. All three
    fail on the old copy-and-unlink shape.

Full suite: 3030 passed.

🤖 Generated with Claude Code

Two defects in the same code path, both of which lose user data.

``upsert_task`` replaced every column on every call. A caller that rewrote a
row for some other reason -- a file move, an appended note -- had to read the
row first and pass ``source``, ``source_url``, ``deadline`` and ``tags`` back,
or the write nulled them. Two bugs came from a caller that did not: reindex
dropped ``tags`` after v018 added the column, and ``task_done`` nulled all
four when it moved a file into done/ (fixed in #238 by adding the four lines
back). The nulled ``source_url`` also broke duplicate detection, because
``_find_duplicate_tasks`` matches that column first. A completed task stopped
matching the source item that created it, so the next sync of that item made a
second task.

These four columns are now preserve-on-omit: an omitted column keeps its
stored value. ``None`` still clears one, and ``""`` still clears ``tags``.
Five call sites stop restoring what they never change -- task_done,
task_write, task_update, the PATCH content save, and TaskManager.mark_done --
and reindex stops merging file values against the stored row by hand.

The move itself wrote the destination and unlinked the source. Both paths are
one path when a task is completed twice, because the first completion stores a
``file_path`` under done/ and the second one reads it back as its source, so
the sequence wrote the file and then deleted it. The task kept its row and
lost its whole markdown file, including the update history. An agent retrying
``task_done`` after a timeout is enough to reach it. ``move_task_file`` now
appends and renames, and a rename onto one path is a no-op.

Co-Authored-By: Claude <noreply@anthropic.com>
@alex-clickhouse
alex-clickhouse requested a review from Copilot August 6, 2026 18:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants