loop-task

Task Chaining

Chain tasks together with context sharing to build multi-step pipelines.

What is task chaining?

Tasks can declare successor tasks via onSuccessTaskId and onFailureTaskId. When a loop's task finishes, it automatically triggers the next task - no manual intervention needed. The stdout from each task is captured, parsed, and passed as context to the next step.

This lets you build multi-step pipelines that react to success or failure, share data between steps, and run complex workflows autonomously.

Creating a chain

You can create and link tasks in three ways.

CLI

Create the first task:

loop-task new 30m -- ./find-issues.sh

This returns a task ID (e.g. task-abc). Create a second task and link it to the first by passing the ID with --on-success:

loop-task new 20m --on-success task-abc -- ./process-issues.sh

Task linking via the CLI uses the --on-success and --on-failure flags. To update an existing task's links, use the TUI or HTTP API.

TUI

  1. Open the board.
  2. Navigate to the Tasks tab.
  3. Create a new task or select an existing one.
  4. Set onSuccessTaskId or onFailureTaskId to the ID of another task.

The board shows the chain as a directed graph - you can visually trace the flow from one task to the next.

HTTP API

Create a task with a successor:

curl -X POST /api/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "command": "./find-issues.sh",
    "timeout": "30m",
    "onSuccessTaskId": "task-xyz"
  }'

Update an existing task's chain links:

curl -X PATCH /api/tasks/task-abc \
  -H "Content-Type: application/json" \
  -d '{
    "onSuccessTaskId": "task-xyz"
  }'

Context sharing

When a task completes, its stdout is captured (up to 1 MB) and automatically parsed into a shared context object. The parser recognises three formats:

FormatExampleNotes
JSON object{"issue": "#42", "priority": "high"}Single top-level object
JSONL{"issue": "#42"}\n{"issue": "#43"}One JSON per line - merged into an array per key
Key:value pairsissue: #42Plain text, one pair per line

The parsed values are merged into a context object that travels downstream. If a later task outputs a value with the same key, it overwrites the previous value.

What gets shared

  • stdout - parsed automatically as above
  • stderr - captured alongside stdout; when stdout is non-JSON, stderr is merged into the {{output}} context key so error messages reach downstream tasks
  • exit code - available as {{exitCode}}
  • task ID - available as {{taskId}}
  • duration - available as {{duration}}

Template interpolation

Task commands support {{key}} template syntax. When the command runs, every {{key}} is replaced with the corresponding value from the shared context.

If task 1 outputs:

{"issue": "#42", "repo": "my-org/my-repo"}

Task 2's command can reference those values:

gh issue edit {{issue}} --add-label "in-progress" --repo {{repo}}

At runtime this becomes:

gh issue edit #42 --add-label "in-progress" --repo my-org/my-repo

How it works

  • Shell escaping is handled automatically - special characters in values are properly escaped before the command is executed.
  • Missing keys produce a clear error at runtime: Unknown context key: {{missingKey}}.
  • Nested JSON paths are not supported - flatten your output to top-level keys.

Multi-step pipeline example

Here is a complete pipeline that finds stale issues, marks them, fixes them, and closes them.

Task 1 - Find stale issues:

{
  "command": "./find-stale-issues.sh",
  "timeout": "10m"
}

Expected stdout:

{"issue": "#123"}

Task 2 - Mark as in-progress (runs on success of task 1):

{
  "command": "gh issue edit {{issue}} --add-label \"in-progress\"",
  "timeout": "1m",
  "onSuccessTaskId": null
}

Set onSuccessTaskId to the ID of task 1 so this runs after task 1 succeeds.

Task 3 - Run the fix (runs on success of task 2):

{
  "command": "opencode run \"fix issue {{issue}}, 3 max\"",
  "timeout": "30m",
  "onSuccessTaskId": null
}

Link this task to task 2's onSuccessTaskId.

Task 4 - Mark as fixed (runs on success of task 3):

{
  "command": "gh issue edit {{issue}} --remove-label \"in-progress\" --add-label \"fixed\"",
  "timeout": "1m",
  "onSuccessTaskId": null
}

Link this task to task 3's onSuccessTaskId.

The pipeline flows:

Task 1 (find stale issues)
  └── onSuccess → Task 2 (add label)
                      └── onSuccess → Task 3 (fix with opencode)
                                          └── onSuccess → Task 4 (mark fixed)

If any task fails, the chain stops - no downstream task runs unless you configure onFailureTaskId for error recovery.

Three interfaces

Every chain management operation is available through all three interfaces.

OperationCLITUIHTTP API
List tasksloop-task statusTasks tab on the boardGET /api/tasks
Create taskloop-task new <duration> -- <command>Create form in Tasks tabPOST /api/tasks
Update chain linksloop-task update <id> --on-success <id>Edit task propertiesPATCH /api/tasks/:id
View chain graph-Visual graph in Tasks tabGET /api/tasks/:id/chain
Delete taskloop-task rm <id>Delete button in task detailDELETE /api/tasks/:id