Keep your fork fresh with AI-powered rebasing.
Forklift runs an AI agent in an isolated container to rebase your fork against upstream, then hands you a local branch to review. If it gets stuck, it writes a STUCK.md explaining what blocked progress.
# Build the container (once)
docker build -t forklift/kitchen-sink:latest docker/kitchen-sink
# Configure OpenCode credentials
cp ~/.config/forklift/opencode.env.example ~/.config/forklift/opencode.env
# Edit opencode.env with your API keys and settings
# Run in your fork
cd your-fork-repo
uv run forklift --debug- Git repo with
origin(your fork) andupstream(source) remotes - Docker
- uv (
pip install uv) - git-filter-repo 2.47.0+ (
pip install git-filter-repo==2.47.0orbrew install git-filter-repo) - Git identity configured (
git config user.name/git config user.email)
uv run forklift --debug
uv run forklift --version| Flag | Description |
|---|---|
--main-branch=<name> |
Target branch if not main |
--target-policy=latest-version |
Rebase to latest stable tag (X.Y.Z or vX.Y.Z) (default) |
--target-policy=tip |
Rebase to upstream branch tip |
--timeout-seconds=<n> |
Override agent timeout (default: 1500) |
--instruction='...' |
Extra instructions provided in addition to FORK.md contents; repeat to add multiple blocks |
--model, --variant, --agent |
Override OpenCode settings per-run |
Preview what's changed upstream before running the full sync:
uv run forklift changelog
uv run forklift changelog --main-branch=dev
uv run forklift changelog --target-policy=latest-versionThis runs entirely on the host with no container launch or history mutation. forklift changelog
accepts the same --target-policy values as the main command, so you can compare against
either upstream/<main-branch> or the latest stable upstream tag. Requires Git 2.38+.
List files that are absent from upstream and are generally safer places to make fork-specific changes:
uv run forklift files
uv run forklift files --main-branch=dev
uv run forklift files --hashThis command uses local refs only, ignores uncommitted files, and prints plain text output for easy piping or agent consumption.
Print the first commit that exists on your fork branch but not on upstream:
uv run forklift first
uv run forklift first --main-branch=devThis command uses local refs only, prints the full SHA of the earliest fork-only commit, and exits non-zero when no fork-only commits exist.
Create ~/.config/forklift/opencode.env (mode 0600):
OPENCODE_API_KEY=sk-...
OPENCODE_VARIANT=production
OPENCODE_AGENT=default-agent
OPENCODE_SERVER_PASSWORD=server-passphrase
# Optional
OPENCODE_ORG=acme
OPENCODE_MODEL=claude-35-sonnet
OPENCODE_TIMEOUT=1500
OPENCODE_SERVER_PORT=4096
# Provider keys (at least one required)
ANTHROPIC_API_KEY=...
OPENAI_API_KEY=...
GOOGLE_GENERATIVE_AI_API_KEY=...
OPENROUTER_API_KEY=...
| Variable | Description |
|---|---|
FORKLIFT_DOCKER_IMAGE |
Alternate container image |
FORKLIFT_DOCKER_ARGS |
Extra docker run flags (GPU, proxies, etc.) |
DOCKER_BIN |
Override Docker CLI path |
Add a FORK.md to your repo root to give the agent context about your fork. If the repo-root file is absent, Forklift falls back to .agents/FORK.md. See the template for format and examples.
For one-off run guidance, pass --instruction='...' on the main forklift command. Forklift appends those blocks as a separate host-provided ## Extra Run Instructions section in the agent prompt; FORK.md remains the durable fork baseline.
FORK.md front matter is optional, but when present it is strict and machine-validated.
setupruns once in/workspacebefore agent launch with a 600-second (10-minute) timeout.rebase.continue_checkruns from/workspacebefore every mediatedgit rebase --continue.- Continue checks must exit zero and leave tracked, staged, and untracked workspace state unchanged.
- Forklift snapshots the active continue check into
/harness-state/rebase-continue-check.shbefore the agent starts, so editing the workspace copy ofFORK.mdduring the run does not change enforcement. - Explicit
git rebase --skipdecisions are recorded and appended to the final terminal completion summary underSkipped Commits. git rebase --abortis rejected untilSTUCK.mdexists and contains non-whitespace content.
Run artifacts are stored in ~/.local/state/forklift/runs/<project>_<timestamp>/:
| Path | Description |
|---|---|
workspace/ |
Cloned repo where the agent worked |
workspace/STUCK.md |
Written if the agent couldn't complete |
control/ |
Host-owned control mount used for live structured rebase events during the run |
harness-state/rebase-continue-check.sh |
Frozen copy of FORK.md rebase.continue_check for the active run |
harness-state/rebase-skipped-commits.json |
Host-owned metadata for explicit agent git rebase --skip decisions |
harness-state/opencode-client.log |
Agent transcript |
harness-state/opencode-server.log |
Server bootstrap log |
harness-state/setup.log |
Mirrored setup diagnostics and command output |
opencode-logs/ |
Full OpenCode debug traces |
Forklift now emits live top-level Rebase N/Total, Conflict N/Total, Continue N/Total, Skip N/Total, Auto-skip N/Total, and Rebase complete log lines while the container is still running. Those structured events travel over a dedicated bind-mounted Unix domain socket in control/; Container stdout / Container stderr remain post-exit human-log surfaces, and opencode-client.log remains the deep transcript artifact.
On success, Forklift publishes a review branch: upstream-merge/<timestamp>/<branch>.
Run directories older than 7 days are automatically pruned.
flowchart LR
A[Your repo] --> B[Snapshot clone]
B --> C[Docker sandbox]
C --> D{Success?}
D -->|Yes| E[Review branch]
D -->|No| F[STUCK.md]
style A fill:#e1f5fe
style C fill:#fff3e0
style E fill:#e8f5e9
style F fill:#ffebee
- Fetches
originandupstream, checks if sync is needed - Creates isolated workspace clone (remotes stripped)
- Launches container with AI agent
- Agent rebases onto upstream target, while Forklift mediates
git rebase --continue|--skip|--abort - Success: rewrites commits to your identity, publishes
upstream-merge/...branch - Failure: writes
STUCK.mdfor inspection, and the terminal completion summary always appendsSkipped Commits
uv run basedpyright # Type checking
docker build -t forklift/kitchen-sink:latest docker/kitchen-sink # Rebuild container