In cloud mode, projects represent git repositories cloned on the runner. Each project goes through a lifecycle from cloning to ready, and serves as the parent for terminal sessions that execute in Docker containers with the project's code.
POST /api/v1/projects
│
▼
┌────────┐ clone success ┌───────┐
│Cloning │ ──────────────────── ▶│ Ready │
└────────┘ └───────┘
│ │
│ clone failure │ DELETE
▼ ▼
┌────────┐ POST /retry (removed)
│ Failed │ ──────────────┐
└────────┘ │
▲ │
│ clone failure │
└────────────────────┘
States:
- Cloning —
git cloneis in progress - Ready — clone complete, sessions can be created
- Failed — clone failed;
clone_errorfield contains the error message. Can be retried.
See REST API for full endpoint documentation. Summary:
| Endpoint | Method | Description |
|---|---|---|
/api/v1/projects |
POST | Create project (starts clone) |
/api/v1/projects |
GET | List all projects |
/api/v1/projects/{id} |
GET | Get project by ID |
/api/v1/projects/{id} |
DELETE | Delete project |
/api/v1/projects/{id}/retry |
POST | Retry failed clone |
All git operations use tokio::process::Command for async execution (see ADR-004).
- Runs
git clonewith progress reporting via the event bus - Timeout configurable via
git.clone_timeout_secs(default: 300s) - Clone progress emitted as
CloneProgressevents via SSE
Each session gets its own git worktree created from the project's clone. This provides isolation — multiple sessions can work on different branches of the same repository without interference.
Every git URL is validated before any network activity:
- Scheme check — only
https://is accepted - Host parsing — extracts hostname, handles IPv4, IPv6, and bracketed literals
- Private IP rejection — blocks:
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16- IPv6 loopback (
::1), link-local, unique-local localhost,0.0.0.0
- DNS resolution check — resolves hostname and verifies all resolved IPs are public
- Allowlist — if
git.allowed_hostsis non-empty, only listed hosts are permitted
Source: runner/src/project_manager.rs
Projects are stored in SQLite via the Store layer. Fields:
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
name |
String | Human-readable name |
git_url |
String | Repository URL |
local_path |
String | Path to local clone |
state |
Enum | Cloning / Ready / Failed |
clone_error |
String? | Error message when Failed |
created_at |
DateTime | Creation timestamp |
updated_at |
DateTime | Last state change |
deleted_at |
DateTime? | Soft-delete timestamp |
Source: runner/src/store.rs
State changes emit RelayEvent::ProjectStateChanged events, which are:
- Broadcast to all SSE subscribers
- Buffered in the ring buffer for reconnect replay
Clone progress emits RelayEvent::CloneProgress with percentage and status message.