Skip to content

JSON Output

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

JSON Output

Every git task command accepts a global --format json flag (it can go anywhere: git task ls --format json or git task --format json ls). It prints exactly one JSON document on stdout and nothing else: no hint text, no automation chatter, no partial lines. That holds on failure too, so it's always safe to parse the same way regardless of outcome.

This is the contract to build any external tool (script, CI check, dashboard, bot) against. Web Server Integration builds directly on what's documented here.

The envelope

// success
{
  "ok": true,
  "command": "new",
  "version": "1.0.0",
  "data": { /* command-specific, see below */ },
  "warnings": []
}

// failure: still on stdout, still exactly one document; the process exit code is 1
{
  "ok": false,
  "command": "show",
  "version": "1.0.0",
  "error": {
    "kind": "not_found",
    "message": "no task matching 'deadbeef'",
    "causes": [],
    "context": { "query": "deadbeef", "entity": "task" }
  },
  "warnings": []
}

Check ok first if you want the structured reason for a failure; don't rely on the process exit code alone to distinguish a validation problem from a genuine crash, since both exit 1. version is git-task's own version, so a long-lived integration can detect a shape change if the response format ever evolves.

warnings[] carries non-fatal issues collected during the command (each with message, an optional detail, and an optional scope, e.g. the name of one repo skipped during a multi-repo ls). A response can be ok: true and still carry warnings.

stdout carries only the JSON document. Even on failure, git-task still writes a human-readable ✖ Error: ... line to stderr, unaffected by --format json. This is deliberate, so errors stay visible when a JSON caller's own logging pipes stderr somewhere a person reads. If you're capturing output, keep stdout and stderr separate and parse only stdout.

Error kinds

error.kind is one of:

Kind Meaning
not_a_repo Not run inside a git repository.
identity_missing No usable name/email in git config to attribute a write to.
not_found The id/name/entity referenced doesn't exist.
ambiguous_id A hash prefix matched more than one task.
validation Bad input: a missing required field, conflicting flags, a malformed expression.
conflict e.g. adding a label/link that's already present, or a task edited concurrently by another writer (see Web Server Integration).
rejected A remote rejected a push (non-fast-forward), same as git itself.
remote A network/remote operation failed for another reason.
io A filesystem operation failed.
internal Anything not otherwise classified. Exhaustive classification isn't the goal; expect to see this occasionally, it doesn't mean something is broken.

error.context is a free-form object whose keys depend on kind, present only when there's something useful to attach. Common ones: not_found carries query/entity; ambiguous_id carries query/matches; validation carries field/missing.

TaskJson: the shape a task takes in every response

Any response that includes a task (show, export, each entry of ls, and the task field of every mutating command's response) uses this shape:

Field Type Notes
id string The real id (creation commit hash).
display_id string KEY-<hash prefix>, e.g. SRV-9057e58a.
key string This repo's effective address key.
title, description string
kind string bug | story | task | epic | subtask.
status string Free-form.
priority string or null low | medium | high.
assignee string or null Email.
assignee_name string or null Resolved display name, already looked up for you.
reporter string Email of whoever created the task.
reporter_name string Resolved display name.
labels, fixed_versions, affected_versions array of string Always present, empty array if unset.
due, milestone string or null
parent string or null Resolved local id for a same-repo parent; null for a cross-repo one or no parent.
parent_display_id string or null
parent_repo string or null null for same-repo/no parent; the target repo's identifier for a cross-repo parent.
children array Only populated by show. Every other response leaves it empty rather than paying for the scan; see ChildJson below.
links array of LinkJson See below.
comments array of CommentJson id, author, author_name, timestamp, text, edited.
deleted boolean
created, updated number Unix timestamps.
history array, or omitted The full op-chain (see below). Only present on show/export, or ls --with-history.

LinkJson: kind (blocks \| relates \| dup), target (resolved local id, or null for a cross-repo link), target_display_id (always present), target_repo (null for a same-repo link, the target repo's identifier for a cross-repo one).

ChildJson: id (null for a cross-repo child), display_id, title, kind, status, repo (null for a same-repo child).

Every *_name field is already resolved from the repo's contributor directory; don't re-derive a display name from an email yourself.

Mutation responses

new, edit, status, comment, label, version, epic, link, and delete all return this shape as data:

{
  "task": { /* TaskJson, without history */ },
  "ops": ["SetStatus", "AddLabel"],
  "automation": [ /* AutomationEvent[], any rules that fired as a result */ ],
  "created": true    // only present, and true, for `new`
}

task reflects the state after automation has run: if a rule changed something the command itself didn't touch, this is what you'll see, not the state right after your own edit. ops is just the tags of the operations your own command appended; a fired rule's own operations live inside its own automation[] entry instead, so you can tell which changes were yours and which were automatic.

Command-specific shapes worth knowing

  • ls: data is { scope, filters_applied, repos: [{ name, project, path, key, branch, tasks: TaskJson[] }], contributors, statuses, total }. contributors is an email-to-name map merged across every repo in the response; statuses is the distinct set of status strings seen. Pass --with-history to include each task's history; omitted by default to keep the listing small.
  • export: data is a bare array of TaskJson (always with history included), not wrapped in an object.
  • show: data is a single TaskJson, with history and children[] populated.
  • log: does not currently branch on --format json; it always prints the human-readable audit trail. For a machine-readable operation history, read history from show --format json (or export) instead.
  • config show / fields / config rule list: data is the effective config document; see Configuration.
  • repos / projects / register / unregister / project ...: data includes the complete, current registry (repos: RepoEntryJson[]) after the change, so a caller refreshes its view in one round trip rather than issuing a follow-up repos call.
  • push / pull: data.refs/data.tasks report a per-task outcome (ok/rejected for push; new/fast_forwarded/merged/up_to_date for pull).
  • whoami: data is { repo, global, effective }, each an identity document (name, email, source), letting a script check what identity would be used before it writes anything.

Practical notes for scripting

  • A missing required field on new (or any command that would otherwise prompt) never blocks on stdin under --format json, even at a real terminal: it returns a validation error listing exactly what's missing. Check git task fields --format json ahead of time if you're generating tasks from another system and don't already know the repo's requirements.
  • Anything that would otherwise be interactive-only (bare edit, bare config rule add, automation add) returns a validation error under --format json instead of hanging; the error message names the flags to pass instead.
  • --repo/--project selectors, and the address form (SRV-hash vs. a bare hash), behave identically under --format json and in text mode; only the output shape changes.

Next: Web Server Integration for turning this into an actual service.

Clone this wiki locally