Storage
Everything PABLO creates at runtime lives in ~/.pablo/, never inside your repositories or worktrees. This page maps that folder and describes a task record.
The ~/.pablo folder#
~/.pablo/
├── config.yaml # global defaults
├── projects/*.yaml # one file per project
├── state/<project>/<branch>.json # one task record per file
├── state/<project>/<branch>.lock # per-task lock
├── stamps/<project>.{sync,poll,main} # last successful run of each job (+ dispatch.lock)
├── agents/ # headless runs: pid files and logs
├── analytics/<project>/YYYY-MM.jsonl # analytics events
├── logs/ # sync logs (rebase-last-<project>.json), macOS service logs
├── cache/ # small lookup caches (e.g. Jira site ids)
└── worktrees/<repo folder name>/ # task worktrees, unless worktrees_root is setBackups go to ~/.pablo-backups/ by default (see Backups). Each part can be moved with an environment variable.
All files are written atomically (to a temporary file, then renamed), with UTC timestamps in ISO-8601.
Task records#
One JSON file per task. pablo task:info current prints it for the current worktree.
| Field | Content |
|---|---|
project, branch, worktree_path | which task this is |
state, state_entered_at | current state and since when |
issue | provider, key, URL, title and status, or null for a prompt task |
summary, prompt | for prompt tasks |
state_before_waiting | the state to restore after a pause |
task_analyst_ran, startup_script_ran | so they only run once per task |
agent_launches | per agent: when it was launched and how many attempts, used to relaunch launches that never started |
needs_testing_entered_at, last_handled_signal_at, last_seen_issue_status | baselines for the QA failure signal |
pr_number, merged | the pull request |
created_at, updated_at | timestamps |
The record also holds a display cache written by the poller, the rendered cells of pablo task:list, so the listing and the dashboard show instantly without calling GitHub. A task the poller hasn't seen yet has no cache, and the listing fetches live instead.
Locks#
Sync, the poller and every command that changes a task take that task's lock (flock on its .lock file), so two of them never work on the same task at once. If the holder crashes, the system releases the lock. The holder writes who it is into the file, to help debugging, and waiting for a lock times out with a clear error instead of hanging. The background job has its own global lock in stamps/dispatch.lock.