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.shThis 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.shTask 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
- Open the board.
- Navigate to the Tasks tab.
- Create a new task or select an existing one.
- 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:
| Format | Example | Notes |
|---|---|---|
| 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 pairs | issue: #42 | Plain 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-repoHow 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.
| Operation | CLI | TUI | HTTP API |
|---|---|---|---|
| List tasks | loop-task status | Tasks tab on the board | GET /api/tasks |
| Create task | loop-task new <duration> -- <command> | Create form in Tasks tab | POST /api/tasks |
| Update chain links | loop-task update <id> --on-success <id> | Edit task properties | PATCH /api/tasks/:id |
| View chain graph | - | Visual graph in Tasks tab | GET /api/tasks/:id/chain |
| Delete task | loop-task rm <id> | Delete button in task detail | DELETE /api/tasks/:id |