← writing

An append-only task log still has to be merged by something

Two git task trackers both use an append-only log. We ran the same 24 merges through each. Which one is safer depends on where your merges happen.

Search for an append-only task tracker that survives git merges and the first result today is taska: a Rust CLI that keeps every task change as a line in one JSONL log, with a custom git merge driver that reconciles two branches field by field. Its tagline is that your branches merge cleanly.

kadence makes the same promise by a different route. It also never rewrites a task in place, but it writes one file per event instead of one shared log, and registers no merge driver at all.

Two designs, one claim. So we ran both through the same merges and wrote down what git said.

What was run

Four scenarios, each starting from a commit with two tasks, then two branches that each make one change, then a plain git merge:

#Branch aBranch b
1moves task onemoves task two
2changes the status of task onechanges the priority of task one
3marks task one donemarks task one in progress
4creates a taskcreates a different task

Scenario 3 is the only one where the two branches genuinely disagree. It is also the exact conflict a real repository hit seven times: a status line, Done against In Progress.

Each scenario ran in three places. In the clone where the tool had been set up. In a fresh clone where nothing but git had run. And in a fresh clone where one read-only command (ta list, kadence task list) ran before the merge. That is 24 merges. The script is short, and it was run twice with the same result both times.

What git said

taska, set-up clonetaska, fresh clonetaska, after one commandkadence, any clone
1 · different taskscleanconflictcleanclean
2 · different fieldscleanconflictcleanclean
3 · same fieldstops, asksconflictstops, asksclean, later wins
4 · two new taskscleanconflictcleanclean

Where taska's driver is registered, it does what it says. Scenarios 1, 2 and 4 merge clean, and in scenario 2 both the new status and the new priority survive on the same task.

Where it is not registered, every one of the four merges conflicts. Including scenario 1, where the branches touched different tasks:

{"seq":1,"op":"Create","task_id":"t1","priority":2,"status":"open","title":"One"}
{"seq":2,"op":"Create","task_id":"t2","priority":2,"status":"open","title":"Two"}
<<<<<<< HEAD
{"seq":3,"op":"Update","task_id":"t1","status":"in_progress"}
=======
{"seq":3,"op":"Update","task_id":"t2","status":"closed"}
>>>>>>> b

Nothing is wrong with either line. Both branches appended to the end of the same file, and without the driver git sees two different third lines.

Why "fresh clone" is not a contrived case

The driver's .gitattributes entry is committed and travels with the repository. Its definition, the command git should run, lives in each clone's local git config, and git never copies config. taska's README states this, and handles it: any ta command re-registers the driver. The third column shows that works. After one ta list, the fresh clone behaves exactly like the set-up one.

So the question is not whether taska's driver works. It is whether the tool has run on the machine where the merge happens. There are a few places where it never does:

  • a CI job that merges or rebases before it installs anything
  • a teammate who cloned and ran git pull before their first ta
  • the merge button on the hosting service. On 2026-09-28 the second result for this query was an issue in another project that reports exactly this: the host's server-side merge never runs a custom merge driver. When we re-read it on 2026-10-02 it was closed as completed, and its one comment confirmed the report and gave the workaround: merge the base branch into the PR branch locally and push, rather than trust the host's conflict status. We did not test a hosted merge for this page. We are pointing to the report, not confirming it

We found out about one more by accident. The first time we ran the bench, taska conflicted on every merge, including the four in the clone where its driver was registered. The driver's command is ta git-merge …, called by name, and we had built ta into a folder that was not on PATH. git could not find the driver, so it treated the file as text. A normal install would not hit this. We did because we skipped the installer.

kadence avoids all of that with no cleverness. Every event is its own file with a new name, so two branches never write to the same path, and git's default merge has nothing to reconcile. That is also why the kadence column needs only one heading: set up or fresh made no difference, because there was nothing to set up.

Where taska is better

Scenario 3 is where the two designs really differ, and taska wins it.

With its default on_conflict = "surface" policy, taska finishes the merge tentatively, keeping the branch being merged into, marks the file as conflicted, and ta resolve then prints the disagreement:

1 field conflict(s) were merged tentatively (keeping ours):
  - `t1`.status: ours="closed" / theirs="in_progress" -> kept ours

The resolution is written back into the log with both candidate values, so the decision can be audited later. You can also switch to latest, ours or theirs if you would rather it did not ask.

kadence does not ask. It keeps both moves, and the task's history shows done followed by in progress, but the current status is simply whichever move was made later. We ran the scenario in both directions to check it is time and not merge direction. The later move won both times. Nobody is told that two branches disagreed. For a status field that is usually what you want, since the later move is usually the correction. When it is not, taska's way is safer, and we would rather say that than leave it out.

taska has more to it than this. Numeric += updates from two branches add up instead of colliding. Task schemas are typed. Dependencies form a checked graph. None of that is compared here.

What this does not cover

  • Four scenarios, not a replay of real history. The probe behind the previous post measured how often task files conflict in real repositories. This page only measures what each tool does when they do
  • One machine, one git version, local merges only. The hosted-merge point above comes from someone else's report
  • Rebase was not tested. Both tools say they handle it. We did not check

The one-line version: if every merge in your repository runs on a machine where ta has already run, taska merges as cleanly as it claims, and it handles a real disagreement more carefully than kadence does. If some merges happen anywhere else, a design that needs no driver has one less thing that can be missing.


kadence writes one file per event so that a merge needs nothing but git. The merge test is the assertion behind the clean column above, and the bench script is in this site's repository under docs/search-log/bench/ for anyone who wants to re-run it.

Where the numbers come from

  • Re-read 2026-10-02 with `gh issue view 862 -R remigiusz-antczak/deep-code-review`: state CLOSED, reason COMPLETED, closed 2026-09-21; one comment, which confirms the report and recommends merging the base branch locally and pushing
  • Probe run 2026-09-28: `append-only task tracker git merge driver conflicts` returned github.com/justpresident/taska first, then an issue in remigiusz-antczak/deep-code-review titled 'A git merge-driver resolves conflicts only on LOCAL merge/rebase — the host's server-side merge never runs it', an issue in paiml/aprender, a DEV Community post on an append-only ledger, and five others
  • Bench run 2026-09-28 with taska 1.4.0 (built from justpresident/taska at commit 3054f94), kadence 0.7.0 and git 2.39.5: four scenarios, three clone states, two tools, 24 merges. taska with its driver registered: scenarios 1, 2 and 4 merged clean, scenario 3 stopped with a tentative resolution. taska in a fresh clone with no command run: 4 of 4 merges conflicted in .taska/mutations.jsonl. taska in a fresh clone after one `ta list`: same as with the driver. kadence: 12 of 12 merged clean in every clone state. The script and its full output are in this site's repository under docs/search-log/bench/
  • Scenario 3 run in both directions for kadence on 2026-09-28: the move made later won whichever side of the merge it was on, and `kadence task show` listed both moves in the task's history
  • taska's own documentation: the merge driver definitions live in per-clone local git config and are re-registered on the next `ta` command; on_conflict policies surface, latest, ours and theirs — README of github.com/justpresident/taska at 3054f94