Skip to content

Automation Recipes

KoukeNeko edited this page Sep 9, 2026 · 3 revisions

Automation Recipes

English · 繁體中文

Three working examples of driving Taiga from something other than a keyboard. Each one leans on the JSON contract: --json for a stable shape, --fields to keep the payload small, --no-input so nothing waits for a prompt, and exit codes to decide what happened.

Set TAIGA_API_URL and TAIGA_TOKEN in the environment for all three, or log in first with taiga auth login.

1. Open or update a Taiga issue when CI fails

The trap here is creating a duplicate issue on every failed run. Search first, and comment on the existing issue instead when one is already open.

# .github/workflows/report-failure.yml
name: Report failures to Taiga
on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]

jobs:
  report:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    env:
      TAIGA_API_URL: ${{ secrets.TAIGA_API_URL }}
      TAIGA_TOKEN: ${{ secrets.TAIGA_TOKEN }}
      TAIGA_PROJECT: example-project
    steps:
      - run: curl -fsSL https://raw.githubusercontent.com/KoukeNeko/taiga-cli/main/scripts/install.sh | sh
      - name: Open or update the issue
        run: |
          export PATH="$HOME/.local/bin:$PATH"
          subject="CI failed on ${{ github.ref_name }}"
          run_url="${{ github.event.workflow_run.html_url }}"

          # --fields keeps the response to what the script reads.
          existing=$(taiga search "$subject" --type issue --json --fields ref,subject --no-input |
                     jq -r --arg s "$subject" 'first(.items[] | select(.subject == $s) | .ref) // empty')

          if [ -n "$existing" ]; then
            taiga issue comment "$existing" --body "Failed again: $run_url" --json --no-input
          else
            taiga issue create --subject "$subject" \
              --description "First failure: $run_url" \
              --type Bug --json --no-input
          fi

Two details worth copying rather than the whole file. --fields ref,subject means the script parses only what it uses, so an added field upstream cannot change its behaviour. And nothing retries: if the connection drops mid-write, taiga exits 11 rather than creating the issue twice, and the step fails loudly. That is the desired outcome for a reporting job.

2. Read the backlog from a shell script

--json output is a stable envelope, so jq expressions written today keep working.

#!/usr/bin/env sh
set -eu

# Unassigned open issues, oldest first.
taiga issue list --limit 200 --json --fields ref,subject,status,assignee,is_closed --no-input |
    jq -r '.items[]
           | select(.is_closed == false and (.assignee // "") == "")
           | "#\(.ref)\t\(.status)\t\(.subject)"'

To act on the result, branch on the exit code rather than on message text:

if taiga issue view 42 --json --fields ref,version --no-input > /tmp/issue.json; then
    version=$(jq -r '.data.version' /tmp/issue.json)
else
    case $? in
        3) echo "not authenticated" >&2 ;;
        5) echo "issue 42 does not exist" >&2 ;;
        9) echo "Taiga is unreachable; safe to retry" >&2 ;;
        *) echo "unexpected failure" >&2 ;;
    esac
    exit 1
fi

The full table is in JSON Contract. The codes that matter most to a script are 6 (someone else changed the record), 9 (transport, safe to retry) and 11 (the write may already have happened, do not retry).

3. Let an agent inspect, rehearse, then commit

An agent should not have to guess whether a command is safe to run unattended. Ask the tool.

taiga schema issue create --json --no-input
{
  "data": {
    "command": "issue create",
    "safety": "write",
    "idempotency": "non_idempotent",
    "input_schema": { "...": "JSON Schema draft 2020-12" },
    "output_schema": { "...": "..." }
  },
  "meta": { "contract": 1 }
}

safety and idempotency are the two fields an agent needs: read commands are free to run, write commands need a decision, and non_idempotent means a failed attempt must not simply be repeated.

Rehearse the mutation before making it. --dry-run still contacts Taiga, so it resolves the status name to an ID and checks that the assignee exists, but it guarantees that no write leaves the process:

taiga issue edit 42 --status "In progress" --assignee alice --dry-run --json --no-input
{
  "plan": {
    "action": "edit",
    "target": "example-project#42",
    "would_write": true,
    "performed": false,
    "changes": {
      "assignee": "alice",
      "base_version": 7,
      "description": null,
      "priority": "",
      "severity": "",
      "status": "In progress",
      "subject": null,
      "type": ""
    }
  },
  "meta": { "contract": 1 }
}

Every editable field appears, whether or not it was given, so read the keys you care about rather than assuming the object only holds what you passed.

Then commit, and handle the two answers that are not success:

if taiga issue edit 42 --status "In progress" --base-version 7 --json --no-input; then
    echo "applied"
else
    case $? in
        6)  echo "someone edited it first; re-read and decide" >&2 ;;
        11) echo "the write may have landed; check before retrying" >&2 ;;
        *)  exit 1 ;;
    esac
fi

Passing --base-version makes the intent explicit: apply this change to version 7 or refuse. Without it the command reads the current version itself, which is usually what a human wants and rarely what an unattended agent should rely on.

4. What a concurrent edit actually does

The transcripts below are real, captured against a Taiga 6.10.2 server with two actors editing the same issue. They matter because Taiga compares the version per field, which is less restrictive than a lock and easy to assume wrongly.

Different fields: both edits land

$ taiga issue view 4 --json --fields ref,subject,status,version
{"data":{"ref":4,"status":"New","subject":"Refresh tokens on 401","version":1},"meta":{"contract":1}}

# meanwhile, someone edits the subject in the web UI

$ taiga issue edit 4 --status "In progress" --base-version 1
Updated issue atlas-commerce#4: Refresh tokens on 401 and 403 (version 3)
$ echo $?
0

The write was based on version 1 and still succeeded, because the other person touched subject while this command touched status. Their wording survives, this status change lands, and the version has advanced twice.

The same field: the write is refused

# someone sets the status to "In progress" first

$ taiga issue edit 5 --status Closed --base-version 1
Error: version: The version doesn't match with the current one
$ echo $?
6
$ taiga --json issue edit 5 --status Closed --base-version 1
{"error":{"code":"occ_conflict","message":"version: The version doesn't match with the current one","retryable":false,"details":{"version":"The version doesn't match with the current one"},"upstream_status":400},"meta":{"contract":1}}

Nothing was overwritten and nothing was retried. For a script the correct response to exit 6 is to re-read the record and decide, because the other value may well be the right one.

Clone this wiki locally