Files
fleetd/docs/Worker-Git-Workflow.md
T
Dai Ha 555715ced9
CI / contract (push) Successful in 44s
CI / build (push) Successful in 1m29s
CB-623: point every path at fleet/fleetd after the org transfer
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.
2026-08-23 05:22:04 +02:00

11 KiB
Raw Blame History

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 — SessionManager creates 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):

  1. 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 (user CLAUDE.md, memory, auth, global skills) is already parity for free; only cwd-relative project-local files are the gap.
  2. 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.
  3. Never overlay the git plumbing — the worktree's own .git file/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.json auth, 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.json and wiki/ 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 can git push with no extra credential.
  • gitea is NOT in the project .mcp.json (only jetbrains, intellij-index, bridged). The primary's gitea MCP comes from a global/user config, so workers do not inherit it. A worker gets only the bridge MCP mounted (via --mcp-config launch flag).
  • No gitea CLI (tea) installed; glab is 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/fleetd repo, 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 concerns ANTHROPIC_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):

  1. You are in a git worktree on a dedicated branch — check git status/git branch; do all work here, never on main.
  2. Implement the task; keep commits focused and message them clearly.
  3. Push your branch (git push -u origin HEAD).
  4. Open a PR to main (option A curl, or the decided mechanism) with a title/body describing the change and referencing the ticket.
  5. Reply via fleet_reply with the PR URL, branch name, files changed, and test names — that reply is the whole handoff.
  6. Do not merge; do not touch .mcp.json or wiki/.

Sequencing

  1. Finish + verify CB-301 core (in flight) — registry/FSM.
  2. Extend CB-301 with worktree provisioning (this doc) once the PR mechanism is chosen.
  3. Add the implementer skill + token injection.
  4. CB-302 = wire the worker checkpoint (commit/push/PR) as the release-time handoff.