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 a | Branch b |
|---|---|---|
| 1 | moves task one | moves task two |
| 2 | changes the status of task one | changes the priority of task one |
| 3 | marks task one done | marks task one in progress |
| 4 | creates a task | creates 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 clone | taska, fresh clone | taska, after one command | kadence, any clone | |
|---|---|---|---|---|
| 1 · different tasks | clean | conflict | clean | clean |
| 2 · different fields | clean | conflict | clean | clean |
| 3 · same field | stops, asks | conflict | stops, asks | clean, later wins |
| 4 · two new tasks | clean | conflict | clean | clean |
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"}
>>>>>>> bNothing 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 pullbefore their firstta - 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 oursThe 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