The repo moved lms/claude-bridge -> fleet/claude-bridge -> fleet/fleetd. Gitea redirects hold, so most of this is not urgent, but one line was a real break: the implementer skill posts a worker's PR to a hardcoded repo path, so every worker PR would have gone to the old address. .claude/skills/implementer/SKILL.md the worker PR endpoint (functional) .gitmodules wiki submodule URL CLAUDE.md + wiki/7-Use-Cases.md the canonical block, kept byte-identical README.md clone command and wiki link deploy/bridged.service Documentation= plugin/.claude-plugin/plugin.json homepage + repository docs/*.md issue and wiki links The wiki is not a separate repo. /repos/lms/claude-bridge.wiki returns 404 and lms owned no .wiki entity, so the wiki moved with the repo; both the old and the new wiki SSH URLs resolve to the same sha. Ticket step 4 assumed a second transfer that does not exist.
11 KiB
Worker Git Workflow — worktree · branch · PR
Status: design (defining the fleet's working model). Builds on the CB-301 session manager and the one-shot/no-reuse decision.
Guiding principle — a worker is a full peer of the primary
The whole point of the bridge is the same Claude Code agent running against a different LLM
provider. A worker must be functionally identical to the primary in context and knowledge —
same project + user CLAUDE.md, same skills, same memory, same MCP tools, same local/private
settings — and differ only in ANTHROPIC_BASE_URL/ANTHROPIC_MODEL. The worktree exists
solely for git isolation (a branchable checkout for commits + code reference). It must never
strip the worker of the configuration a main-tree session has. Config parity is a hard
requirement, not a nice-to-have.
Decisions
- One-shot, no reuse (CB-301) — each task gets a fresh worker, torn down after. The PR is the durable artifact; no context is carried across workers.
- Worktree provisioned by the daemon —
SessionManagercreates a dedicated git worktree + branch per session, hydrates it to full config parity (below), and tears it down on release. - Worker opens its own PR — the worker commits, pushes its branch, and opens the PR/MR itself,
returning the PR URL in its
fleet_reply.
Why worktrees (the hazard being fixed)
Today every bridge worker inherits the primary's own working tree as its cwd
(WorkerService cwd resolution → caller cwd). A single worker editing at a time is safe, but two
parallel implementers would stomp each other's files. A worktree per session gives each worker
an isolated checkout on its own branch — the precondition for fanning out implementation work.
Config parity — the worktree is code-only; it must NOT strip the worker
The trap: git worktree add creates a checkout of tracked files only. Untracked / gitignored
files do not come along. So a worker moved from the primary's main tree into a bare worktree
silently loses exactly the local/private configuration that makes it a full peer — while the
shared-tree it runs in today gives it all of this for free. Moving to worktrees must preserve
that, not regress it.
Classify every source of "what a main session knows", by whether a worktree keeps it:
flowchart TD
subgraph keeps["Inherited automatically — no action"]
A["User-global config<br/>~/.claude/CLAUDE.md, ~/.ccs memory"]:::ok
B["Tracked project config<br/>CLAUDE.md, committed .claude/skills, committed .mcp.json"]:::ok
C["CLAUDE_CONFIG_DIR<br/>(daemon already injects per profile)"]:::ok
D["Bridge MCP<br/>(injected via --mcp-config launch flag)"]:::ok
end
subgraph gap["LOST by a bare worktree — must be hydrated"]
E["settings.local.json<br/>local .claude/* overrides"]:::warn
F["local .mcp.json mods<br/>(the M .mcp.json in git status)"]:::warn
G[".env / .envrc / direnv<br/>local secrets + tokens"]:::warn
H["any other gitignored local config"]:::warn
end
keeps --> R["Worker = full peer of primary"]:::goal
gap -->|"overlay step at provision"| R
classDef ok fill:#2f855a,stroke:#22543d,color:#ffffff;
classDef warn fill:#b7791f,stroke:#7b341e,color:#ffffff;
classDef goal fill:#2b6cb0,stroke:#2a4365,color:#ffffff;
Green is inherited by path (home dir / CLAUDE_CONFIG_DIR) or lives in tracked files that the
worktree checks out anyway. Amber is the real gap — untracked local config the worktree drops.
Mechanism — worktree hydration (part of SessionManager.acquire, after git worktree add):
- Inherit by path, don't copy — keep the worker's
$HOME,CLAUDE_CONFIG_DIR, and memory dir identical to the primary's. Everything home-scoped (userCLAUDE.md, memory, auth, global skills) is already parity for free; only cwd-relative project-local files are the gap. - Overlay the untracked project-local set from the primary tree into the worktree — a defined,
configurable list:
settings.local.json(+ any local.claude/*), the locally-modified.mcp.json,.env/.envrc, and any other gitignored config the primary depends on. Symlink (read-only parity, stays live, nothing to go stale) rather than copy where possible; copy only what a worker may write. - Never overlay the git plumbing — the worktree's own
.gitfile/branch is what gives isolation; that's the one thing that must differ from the main tree.
The overlay set lives in config (BridgedConfig.Worker.parityOverlay — a list of repo-relative
paths, with sane defaults) so it's auditable and per-repo tunable.
Trust note (deliberate). Hydrating local config means the primary's local secrets/tokens (
.env,.mcp.jsonauth, gitea token) become visible to an off-subscription worker running against a third-party model endpoint. That is the accepted consequence of "workers must be full peers" — but it is a real trust expansion over a worker that only sees tracked code. Keep the overlay list explicit and minimal; don't blanket-symlink the whole tree..mcp.jsonandwiki/remain excluded from all worker commits regardless of being present for reference.
Lifecycle
sequenceDiagram
autonumber
participant P as Primary
participant SM as SessionManager (daemon)
participant G as git / gitea
participant W as Worker
P->>SM: acquire(ticket, profile)
SM->>G: git worktree add wt -b worker/ticket-nonce main
SM->>SM: overlay parity config into wt
SM->>W: spawn (cwd = wt, on its branch)
Note over W: implement in the isolated worktree
W->>G: git commit + git push (SSH, same user)
W->>G: open PR (branch to main)
W-->>P: fleet_reply (prUrl, branch, summary, tests)
P->>SM: release(paneId)
SM->>G: git worktree remove wt
Note over G: branch + PR persist for review/merge
P->>G: review PR, merge on green
The worker's "checkpoint" (CB-302) is exactly steps 6–8: commit → push → open PR. This replaces the
earlier STATE.md idea — a PR is reviewable, mergeable, and self-describing.
Infra facts (verified this session)
- Remote:
ssh://git@git.ltms.dev:2224/fleet/fleetd.git(gitea). Push is over SSH — a worker running as the same user with the same keys cangit pushwith no extra credential. - gitea is NOT in the project
.mcp.json(onlyjetbrains,intellij-index,bridged). The primary's gitea MCP comes from a global/user config, so workers do not inherit it. A worker gets only thebridgeMCP mounted (via--mcp-configlaunch flag). - No gitea CLI (
tea) installed;glabis present but is the GitLab CLI (wrong backend).
Implication: git push is free for workers; only PR creation needs a new mechanism.
Open decision — how the worker creates the PR
| Option | Mechanism | Trade-off |
|---|---|---|
| A. gitea REST + token | Worker curls POST /api/v1/repos/fleet/fleetd/pulls with a scoped token injected by the daemon into the worker env |
Minimal, no new server; token lives in the off-subscription worker's env (scope it tightly) |
| B. mount gitea MCP into workers | Add the gitea MCP to the worker's --mcp-config alongside bridge |
Clean tool call, but the gitea MCP's own auth/token must be provisioned per worker; more moving parts |
C. install tea CLI |
Worker runs tea pr create with a token |
Another dependency to install + configure; same token question as A |
Recommendation: A (gitea REST + a repo-scoped token). Smallest surface, reuses SSH for push,
and the token is a single scoped secret the daemon injects like it already injects
ANTHROPIC_AUTH_TOKEN. The implementer skill wraps the curl in one documented step.
Trust / token scope (the real cost of "worker opens its own PR")
- Off-subscription workers already could push (SSH, same user). The incremental grant is PR-create, i.e. a gitea API token.
- Scope the token minimally: the
fleet/fleetdrepo,write:repository(create branch + PR), not merge/admin/org. A leaked token can open PRs, not merge them — the primary/human is still the merge gate. - Inject via the daemon (env var, e.g.
GITEA_TOKEN), never written to the worker's config dir — same non-invasive pattern as the ANTHROPIC token. Guard is unaffected (it concernsANTHROPIC_BASE_URL, not git).
Implementation plan
| Piece | Where | Notes |
|---|---|---|
| Worktree provision/teardown | CB-301 ext — SessionManager.acquire/release; WorkerSession gains worktree, branch |
daemon shells out to git worktree add/remove |
| Config-parity overlay | CB-301 ext — SessionManager.acquire, after git worktree add |
symlink/copy the parityOverlay set into the worktree so the worker is a full peer; this is what makes worktrees viable, not a dead-end |
| Overlay config | BridgedConfig.Worker.parityOverlay — repo-relative paths, sane defaults |
auditable, per-repo tunable; keep explicit + minimal (trust) |
| Branch naming | worker/<ticket-slug>-<nonce> off main (or a configured base) |
one branch per session |
| Commit + push + PR handoff | CB-302 — worker-driven, guided by the skill | push = SSH; PR = option A |
| Implementer skill | .claude/skills/implementer/SKILL.md |
worktree-aware playbook (see below); mounts automatically since workers inherit repo cwd |
| gitea token injection | WorkerService env + BridgedConfig |
repo-scoped, minimal perms |
| PR review + merge | Primary (has gitea MCP + judgment) | merge on green; the human/primary gate stays |
Implementer skill (outline)
A worker-facing playbook (sibling to the existing reviewer skill):
- You are in a git worktree on a dedicated branch — check
git status/git branch; do all work here, never onmain. - Implement the task; keep commits focused and message them clearly.
- Push your branch (
git push -u origin HEAD). - Open a PR to
main(option Acurl, or the decided mechanism) with a title/body describing the change and referencing the ticket. - Reply via
fleet_replywith the PR URL, branch name, files changed, and test names — that reply is the whole handoff. - Do not merge; do not touch
.mcp.jsonorwiki/.
Sequencing
- Finish + verify CB-301 core (in flight) — registry/FSM.
- Extend CB-301 with worktree provisioning (this doc) once the PR mechanism is chosen.
- Add the implementer skill + token injection.
- CB-302 = wire the worker checkpoint (commit/push/PR) as the release-time handoff.