Skip to content

Core Concepts

Mohsen Beiranvand edited this page Aug 7, 2026 · 1 revision

Core Concepts

Tasks are events, not files

A task is not a file in your working tree. It's a sequence of operations (CreateTask, SetStatus, AddComment, and so on) stored as a chain of git commits under refs/tasks/<task-id>. Each mutation appends one more commit to that chain; the commit's tree holds a single blob, ops.json, containing the operation(s) that mutation recorded. Reading a task means walking every commit reachable from the ref's tip and folding the operations into a Task struct, in causal order.

This has a few consequences worth internalizing early:

  • No working-tree footprint. git status never shows a dirty task file. git add/git commit on your actual code is unaffected by task activity.
  • Full history for free. Every field change, every comment, every label add/remove is a real, timestamped, attributed commit. git task log <id> walks and prints that chain.
  • The ref's tip advances; the task's id does not. <id> is the creation commit's own object hash. As you append operations, the ref (refs/tasks/<id>) moves to point at the newest commit, but the id you address the task by never changes.

Addressing: KEY-hash vs. the real id

A task's real identity is its creation commit's hash. Typing a full hash is a hassle, so every repo has a short, human-readable address key (e.g. SRV), and tasks display as SRV-9057e58a: the key plus a hash prefix.

The KEY- part is purely cosmetic. It gets stripped before every lookup and whatever's left is resolved as a hash prefix, so it is never validated against the repo's actually-configured key: a task addressed with the wrong prefix, or a bare 9057e58a with no prefix at all, both resolve the same way. Don't hand-construct an id from a title or a guess; always take it from a prior command's output.

The first task created in a repo with no key pinned yet locks one in automatically (derived from the repo's directory name), so it stays stable even if the directory is later renamed or cloned elsewhere. Pin it explicitly with git task config key SRV; see Configuration.

Ordering and merges: why this is a DAG, not a list

Because two clones of the same repo can each append operations to the same task while offline, refs/tasks/<id> is a commit graph, not a straight line. When git task pull finds a task edited on both sides since the last sync, it produces a real two-parent git merge commit joining both histories, the same way git merge would for a branch. No data is lost: both branches' full operation sequences remain reachable, and loading a task always replays every reachable commit in topological order (a Kahn's-algorithm-style sort), falling back to timestamp only to break ties between commits with no ancestor relationship to each other.

This matters practically in one way: conflicts are not resolved field by field. There's no per-field "last write wins" merge strategy. The merge commit just joins two branches; whichever operations land later in the deterministic topological order win for a given field. See Sync and Multi-Repo for the push/pull mechanics this supports.

Per-repo configuration is event-sourced too

A repo's address key, its required-field overrides, and its automation rules are not stored in a working-tree file either. They live under a second, reserved ref, refs/tasks/config, as their own chain of config operations, folded the same way a task's operations are. That means repo config syncs automatically over the same push/pull/clone refspecs as tasks, with no separate export step and no extra directory to check in. It's edited exclusively through git task config ...; there's no file to hand-edit. See Configuration.

Deleting: soft vs. hard

git-task draws a sharp line between two different things people mean by "delete":

  • Soft delete (git task delete <id>) appends a DeleteTask operation like any other mutation. It's recorded in history, syncs to every peer on their next pull, and is hidden from ls by default (ls --deleted shows it). There is no restore; once recorded, it stays.
  • Hard delete (git task drop <id> --force) removes the local ref outright: no history entry, no sync. A peer who already has the task locally will bring it back the next time they push, or you pull from them. Add --remote [name] to also delete it on one named remote, but that reaches only that remote, not every clone that already fetched the task.

If in doubt, delete is almost always the right one, since it's the version that actually propagates. See Task Management.

Identity

git-task never asks who you are. Every write is attributed to whatever git config resolves for the repo: its own .git/config first, falling back to the global ~/.gitconfig. There is no --author flag to override it. Run git task whoami to see, before you write anything, what identity (name/email) a command would be attributed to, at each layer (repo, global, effective).

Clone this wiki locally