← all projects

park

CLI tool backed by SQLite for saving and recalling work context across repos and machines
repo cli context-switching developer-tools go productivity sqlite

park

A CLI tool for saving and recalling human work context, backed by SQLite. (private alternative for GitHub Issues)

You're mid-debug when a colleague asks for urgent help. Park where you are, context-switch, come back and pick up exactly where you left off — right file, right line, right next step.

You're juggling three repos. park list shows everything active across all of them. park list --remote github.com/org/repo scopes it to one.

You context-switch between machines. Set PARK_SYNC_DIR to a synced folder and your parked items follow you — without the database itself ever being shared. See Sync across machines.

Your AI assistant's context window is filling up, or you switch between multiple assistants. Park the current task so the next session can pick up exactly where you left off, without re-explaining everything.

How is park different?

| Tool | What it does | What park adds | |---|---|---| | git stash | Saves uncommitted code changes in the current repo | Saves why you were there, the next step, and context across all repos — no code changes required | | LLM session resume | Re-feeds prior conversation to the AI | A structured, queryable record you write once; the next session starts from facts, not a transcript | | GitHub Issues | Tracks bugs and features for a team | Local-first and private; captures device, branch, and "how to pick up" automatically; spans repos without a remote | | Todo list | Records tasks to complete | Records the full context — where in the code, why it matters, exact next step — not just the task name |

Install

go install github.com/svandragt/park@latest

go install writes the binary to $(go env GOBIN), or to $(go env GOPATH)/bin if GOBIN is unset. Check that command -v park points at the new binary, because if another park comes earlier on your PATH (for example ~/.local/bin/park), that one runs instead. Run park version to confirm the new build before anything destructive such as park rebuild.

Usage

park add --name "fix auth bug" --desc "Session token issue" --body "..." --why "Blocks release" --how "Start at auth.go:42"
park edit <id> --body "updated context" --tags "auth,urgent"
park edit <id> --append-body "new notes"   # adds to the body instead of replacing it; --append-how does the same for how-to-apply
park edit <id> --status resolved   # equivalent to park done <id>
park add --name "child" --parent 240   # group under a milestone (240, #240 or - for most recent)
park edit <id> --parent 240        # set the parent; --parent "" clears it
park add --name "x" --remote git@github.com:me/other.git   # file under another repo's remote
park edit <id> --remote <url>      # move to another remote (clears the branch); --remote "" clears both
park list                          # active items
park list --current                # scope to current git remote + branch
park list --status resolved        # resolved items
park list --remote github.com/org/repo  # filter by repo (SSH or HTTPS format)
park list --branch main            # filter by branch
park list --tag auth               # filter by tag
park list --type bug               # filter by type (task/project/bug/feature/chore/docs)
park list --parent 240             # children of #240, with a done count in the header
park search "JWT"                  # full-text search (porter stemming, active items only)
park search --status all "JWT"     # search across all statuses
park search --tag auth "token"     # search within a tag
park search --type bug "crash"     # search within a type
park search --parent 240 "crash"   # search within a milestone
park search --remote github.com/org/repo "fix"  # search within a repo
park search --current "token"      # search in current git remote + branch
park show <id>                     # full detail
park show -                        # show most recently added item
park show --current                # first active item for the current remote + branch
park done <id>                     # mark resolved
park done -                        # resolve most recently added item
park archive <id>                  # archive
park reopen <id>                   # move back to active
park delete <id>                   # hard-delete an item
park prune --days 30               # hard-delete resolved/archived items older than N days
park migrate <dest-dir>            # copy DB to new location, print PARK_DB export line
park rename-remote <old> <new>     # update remote URL across all items
park retype <old> <new>            # change the type of every item of one type (for example project task)
park sync-seed --i-understand-this-runs-once  # one-time: seed the sync log from this machine
park rebuild --yes                 # rebuild the local database from the sync logs
park serve                         # browse items in a web UI (default 127.0.0.1:7654)
park serve --addr :7654            # listen on a different address
park version                       # print the version (also --version, -v)
park help                          # show usage (also --help, -h)

add automatically captures hostname, git remote, and current git branch. If the remote has been renamed (e.g. a GitHub repo rename), add detects the redirect and updates all existing items to the canonical URL automatically.

The default type is task. Use type project for a milestone, then point its items at it with --parent. show lists a milestone's children with a done count. Resolving a milestone that still has open children prints a warning and goes ahead. Deleting or pruning a milestone clears parent on its children.

Configuration

| Variable | Default | |---|---| | PARK_DB | ~/.local/share/park/park.db (XDG-aware) | | PARK_SYNC_DIR | unset; when set, park writes and reads sync logs there |

Sync across machines

To use park on more than one machine, give each machine its own log file in a shared folder. Set PARK_SYNC_DIR to a folder your file syncer keeps in step, and leave PARK_DB on its local default:

export PARK_DB="$HOME/.local/share/park/park.db"
export PARK_SYNC_DIR="$HOME/sync/park"

Each machine appends its changes to <hostname>.jsonl and never writes another machine's file, so the syncer has no conflicts to resolve. On each run, park folds every log it can see into the local database. Every machine ends up with the full set of items.

Set this up once, on the machine that already holds your items:

park sync-seed --i-understand-this-runs-once

Run it on exactly one machine. It writes every existing item to that machine's log. A second run, or a run on a second machine, duplicates every item under a new identifier. The command refuses if this machine's log already has entries.

On every other machine, build the local database from the logs:

park rebuild --yes

rebuild discards the local database and rebuilds it from the logs alone, so run it only when the logs hold everything you want to keep.

When two machines change the same item, the later change wins. Items are matched by a generated identifier, not by the number you see in park list, which stays local to each machine.

Do not put the database in a synced folder

Earlier versions suggested pointing PARK_DB at a synced folder. Don't. SQLite writes a database as several files that must stay in step, and a file syncer copies them one at a time. A half-copied set fails to open with database disk image is malformed, and turning off write-ahead logging narrows that window without closing it.

PARK_SYNC_DIR avoids the problem: a log file is append-only and has exactly one writer, which every file syncer handles safely.

Claude Code skill

A skill for Claude Code is included in the repository. It lets you park and resume context using natural language ("park this", "show parked", "work on #2").

To install, copy the skill into Claude's skills directory:

mkdir -p ~/.claude/skills/park
cp .claude/skills/park/SKILL.md ~/.claude/skills/park/SKILL.md

Once installed, Claude will recognize park-related phrases and call the park CLI automatically.

Surface parked items at session start

park list only gets run when you already suspect something is parked. To make the agent tell you instead, install a SessionStart hook:

park hook --install

This adds park hook run to ~/.claude/settings.json (Claude Code) and ~/.codex/hooks.json (Codex), for whichever of the two exists. Both agents use the same hook format. Run park hook on its own to print the config block without writing anything, or pass --agent claude, --agent codex, or --settings <path> to target one file.

At the start of each session, park hook run reads the session's working directory, resolves its git remote, and injects that repo's active items as session context, one line per item with its name and tags. Tags that the name already contains are left out, so more items fit in the part of the context the agent reads. To keep every tag, change the hook command to park hook run --compact=false. The agent runs park search or park list --tag for the detail. You also see a short summary of the parked items, so you know the hook ran. It prints nothing when there is no remote, no database, or nothing parked — a session that opens with a hook error is worse than one with no hook.

The installer merges into any existing SessionStart hooks rather than replacing them. Hooks load at session start, so the change takes effect next session; in Claude Code, /hooks forces a reload.

Web UI

To browse parked items in a browser instead of the terminal, run:

park serve

This starts a local server at http://127.0.0.1:7654 with full-text search, a repo filter, and clickable tags and types. Pass --addr to change the listen address. The server binds to localhost by default, so the UI stays private to your machine.

The UI is read-only, so park serve copies the database once at startup and serves that snapshot. It never holds the live database open, and it shows the items as they were when the server started.

Build

go build ./...
go test ./...

To cut a release, see RELEASING.md.