Jobs API
Submit one-shot agent tasks, monitor their progress, and cancel running jobs — all via REST.
Each job launches an agent in an isolated git worktree, sends it a prompt, and monitors it until completion. Jobs appear in the Jobs sidebar in the Coral dashboard, separate from persistent live sessions.
Submit a task
POST /api/tasks/run
Queues a new agent task. Returns immediately — all work (worktree creation, agent launch, monitoring) happens in the background.
Request body
| Field | Type | Default | Description |
|---|---|---|---|
prompt |
string | required | The instruction sent to the agent. |
repo_path |
string | required | Absolute path to the git repository. |
agent_type |
string | "claude" |
Agent to use (claude or gemini). |
base_branch |
string | "main" |
Branch to create the worktree from. |
create_worktree |
bool | true |
Create a fresh git worktree for the run. |
cleanup_worktree |
bool | true |
Remove the worktree when the run finishes. |
max_duration_s |
int | 3600 |
Timeout in seconds. The run is killed after this. |
flags |
string | "" |
Extra CLI flags passed to the agent. |
display_name |
string | null |
Label shown in the UI. Defaults to Task #<run_id>. |
webhook_url |
string | null |
URL to receive POST callbacks on status changes. |
auto_accept |
bool | false |
Auto-respond to permission prompts (also adds --dangerously-skip-permissions). |
max_auto_accepts |
int | 10 |
Safety limit — auto-accept disables after this many acceptances. |
Example
curl -X POST http://localhost:8420/api/tasks/run \
-H "Content-Type: application/json" \
-d '{
"prompt": "Add input validation to the signup form",
"repo_path": "/home/user/myproject",
"display_name": "Signup validation",
"auto_accept": true,
"max_auto_accepts": 20,
"webhook_url": "https://example.com/hooks/coral"
}'
Response
{"run_id": 42, "status": "pending"}
Errors
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "'prompt' is required"} |
Missing prompt field. |
| 400 | {"error": "'repo_path' is required"} |
Missing repo_path field. |
| 400 | {"error": "repo_path '...' does not exist"} |
Path is not a directory. |
| 429 | {"error": "Concurrent task limit reached (max: 5). Try again later."} |
At capacity. |
Poll run status
GET /api/tasks/runs/{run_id}
Response
{
"id": 42,
"status": "running",
"trigger_type": "api",
"session_id": "a1b2c3d4-...",
"display_name": "Signup validation",
"worktree_path": "/home/user/myproject_task_run_42",
"scheduled_at": "2025-03-11T10:00:00+00:00",
"started_at": "2025-03-11T10:00:05+00:00",
"finished_at": null,
"exit_reason": null,
"error_msg": null
}
| Field | Values |
|---|---|
status |
pending, running, completed, killed, failed |
exit_reason |
agent_done, timeout, user_cancelled, or null |
Returns {"error": "Run not found"}, 404 if the run_id doesn't exist.
Cancel a run
POST /api/tasks/runs/{run_id}/kill
Kills the tmux session, sets status to killed with exit_reason: "user_cancelled", and fires the webhook callback.
Response
{"ok": true}
Returns {"error": "Run not found or not active"}, 404 if the run doesn't exist or is already in a terminal state.
List one-shot runs
GET /api/tasks/runs?limit=50&status=running
Returns recent API-triggered runs (excludes cron-scheduled runs).
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 50 |
Max results (1–200). |
status |
string | null |
Filter by status. |
Response
{
"runs": [{ /* same shape as poll response */ }]
}
List all active runs
GET /api/tasks/active
Returns all pending/running runs across both cron and API-triggered jobs.
{
"runs": [{ /* run objects with additional job_name field */ }]
}
Job lifecycle
A submitted job moves through these states:
POST /api/tasks/run
|
v
pending ──── run record created
|
| (background)
v
[worktree creation] ──── git worktree add <repo>_task_run_<id> <branch>
| failure → failed
v
[agent launch] ──── new tmux session with agent CLI
| failure → failed, worktree cleaned up
v
running ──── session_id written, webhook fires "running"
| prompt sent to agent after 2s init delay
| watchdog starts polling tmux every 30s
|
├── agent exits normally → completed (exit_reason: agent_done)
├── timeout reached → killed (exit_reason: timeout)
└── POST /kill called → killed (exit_reason: user_cancelled)
|
v
[cleanup] ──── auto-accept state removed
worktree removed (if cleanup_worktree: true)
webhook fires terminal status
Worktrees
When create_worktree: true (the default), each job gets an isolated copy of the repo:
- Path:
<repo_path>_task_run_<run_id>(sibling directory to the repo) - Branch: Checked out from
base_branch - Cleanup: Removed automatically when the run finishes (unless
cleanup_worktree: false)
Set create_worktree: false to run the agent directly in repo_path — useful for tasks that don't modify files.
Auto-accept
When auto_accept: true, two mechanisms handle permission prompts:
--dangerously-skip-permissionsis added to the agent's CLI flags, which skips most prompts at the agent level.- Notification-based fallback — if the agent fires a
notificationevent (indicating a prompt the flag didn't cover), Coral sendsy+ Enter to the tmux session after a 0.5s delay.
Safety limit
The max_auto_accepts parameter (default: 10) caps how many times the fallback mechanism fires. Once the limit is reached, auto-accept is permanently disabled for that run and a warning is logged. This prevents runaway acceptance if something goes wrong.
!!! warning
Auto-accept is inherently risky. The agent may accept destructive operations. Use max_auto_accepts to bound exposure, and prefer reviewing agent output in the dashboard for sensitive tasks.
Webhooks
If webhook_url is set, Coral sends HTTP POST callbacks at each status transition.
Payload
{
"run_id": 42,
"session_id": "a1b2c3d4-...",
"status": "completed",
"exit_reason": "agent_done",
"started_at": "2025-03-11T10:00:05+00:00",
"finished_at": "2025-03-11T10:30:00+00:00",
"duration_s": 1795,
"source": "coral"
}
- Fires on:
running,completed,killed,failed finished_atandduration_sarenullfor therunningcallback- Retries: up to 3 attempts (delays: 5s, 15s, 60s)
- Timeout: 10s per attempt
- Any 2xx response is treated as success
Concurrency limits
Coral limits concurrent running jobs to prevent resource exhaustion.
| Setting | Default | Description |
|---|---|---|
CORAL_MAX_CONCURRENT_JOBS |
5 |
Environment variable, set before starting the server. |
When the limit is reached, POST /api/tasks/run returns HTTP 429. The cron scheduler also skips firing jobs while at capacity.
!!! note The concurrency count is based on in-memory watchdog tasks. A server restart resets the count, which could temporarily allow more runs than the configured limit.