Skip to content

[feat] Close the #1080 gaps that #1175 leaves open #1246

Open

Description

@SaulMoro

Parent: #818
Follow-up to: #1080, #1175
Implemented in: #1251

Goal

Close the workspace gaps that remain once #1175 lands, before the drivers, delivery, resume, scorers and benchmarks build on that contract.

flowchart LR
    I1080["#1080 workspaces<br/>(PR #1175)"]:::done
    FU["this issue<br/>F1-F7"]:::added
    I1082["#1082 requirements"]
    I1085["#1085 step scorers"]
    I1086["#1086 session driver"]
    I1087["#1087 Paseo driver"]
    I1090["#1090 PR delivery"]
    I1180["#1180 benchmarks"]
    I1080 --> FU
    FU -- F3 --> I1082
    FU -- F4 --> I1085
    FU -- "F1 F2 F3 F5" --> I1086
    FU -- "F2 F3 F4 F5" --> I1087
    FU -- "F1 F2" --> I1090
    FU -- "F2 F6" --> I1180
    I1086 -- "run lookup by id" --> I1087
    classDef added fill:#2f8f4f,stroke:#1f6b39,color:#fff;
    classDef done fill:#4a5f7f,stroke:#33445c,color:#fff;
Loading

Example

# ship-issue, excerpt
steps:
  - id: change
    workspace: new
    workflow:
      steps:
        - id: implement
          prompt: Implement issue {{ inputs.issue }}.
        - id: test
          needs: [implement]
          command: npm test        # no workspace: read-only
step        runs in (today)          sees           npm test   with F4
implement   change copy              the change     -          change copy
test        member checkout (base)   the base       passes     change copy → fails   ← the change broke a test; today nobody sees it
step   commits                         today                         with F1
a      .tclaude/settings.json (hook)   -                             -
b      runs tclaude on a's branch      a's hook runs, holds a token  base settings.json; hook does not run

Scope

  • F1. Reset every agent configuration path to the run's base. AGENT_PATHS (src/workflow/worktree.ts:12) misses the project directories of four tools the local driver runs (src/workflow/local-tool-adapter.ts:13): .claude-internal, .tclaude, .codex-internal and .tcodex, which hold their settings and hooks (src/types.ts:481-499). It also misses .mcp.json and opencode.json (src/types.ts:462, :702), and Discussion: Workflow Runtime Integration #818 names .mcp.json as agent configuration. [feat] Add workflow workspaces, branches, and session continuation #1080's last criterion says hooks or equivalent agent configuration committed by an earlier step do not run in a later step that holds a token. Keep today's paths, including .agents and .cursor, and add each toolPaths entry's project configuration and mcpProject, not only the tools the local driver runs, since [feat] Add the session skill driver and invocable workflows #1086 and [feat] Add a Paseo driver adapter #1087 run other tools. Export the list so [feat] Deliver workflow branches as pull requests #1090's protected-path check uses it, plus delivery-only paths such as .github/workflows/.
  • F2. Record the run base at workflow start. headCommit returns { ref: 'HEAD', commit } (worktree.ts:78-83), and run.base comes from whichever driver first sends base to next with a new or continue lease (engine.ts:930); next accepts it from any driver (engine.ts:1103-1109). When workflow start runs in a git checkout, it records the commit, the branch HEAD points to (left out on a detached HEAD), and the checkout the run started in, kept in local state only. A command-only run outside a git checkout still starts, and the first next that sends base sets it, as today. A later base from another driver is ignored or checked against run.base. The lease parser requires a string ref (local-driver.ts:321) and, when it is missing, drops the whole workspace, so the step runs in the member's checkout. It should accept a missing ref, and a lease whose workspace does not parse fails its step.
  • F3. A held-back step must not block other ready steps. leaseNext returns wait at the first held-back step (engine.ts:925). Repro at 6529f53: steps a (workspace: new) and b (needs: [], read-only), driver can.workspace: [read-only]; next answers wait and never leases b. An idle local driver turns that wait into busy (local-driver.ts:850). Skip the held-back step for this driver and try the next; answer wait only when every ready step is held back, and list them. [feat] Enforce workflow requirements and execution limits #1082's requirement checks would reuse this path.
  • F4. A read-only step inside a copy runs in that copy. A command with no workspace is read-only "even inside a workspace a parent opened" and runs in the member's checkout (docs/guide/workflow.md:140; placeOf, local-driver.ts:357-359), and so does a prompt with workspace: read-only, since planWorkspace returns before any lineage (workspace.ts:242). The example above is the result. The decision on [feat] Add workflow workspaces, branches, and session continuation #1080 reads "If it changes the active checkout, the step fails"; inside a copy, the active checkout is the copy. A read-only step nested in a copy is leased to the driver that owns the copy, by the owner check a continue step gets (workspace.ts:260-263), runs in that copy with the same dirty check, and runs one at a time with the copy's other steps, as feat(workflow): add workspace branches #1175 already does per checkout. The pass records the commit it read, so a reviewer and its scorer ([feat] Add workflow scorers and recorded quality labels #1085) see the same range. A read-only step outside any copy keeps running in the member's checkout.
  • F5. Check changes against the lease. report accepts any changes.branch and changes.base (engine.ts:1063-1072) and records a copy under that name (:1209-1213), so it can answer cleanup: true while a later step still continues the leased branch. Reject a report whose changes.branch or changes.base differs from the attempt's lease.
  • F6. Observe a workspace step through the worktree's own config. loadAgent, observeLaunch and workflowLaunch receive the member checkout's config (local-driver.ts:578, :603, :612). The worktree has had its own pull record since 545baf5, so the step records the member checkout's team revision, and its tool pulls in the background while it runs (src/launch-context.ts:39-56). Pass the config detectProjectConfig(dir) returns for the worktree.
  • F7. Record bad_session. bad_session (workspace.ts:206, :209) is missing from RECORDED_FAILURES (records.ts:33-38), so the run record drops it.

Out of scope

Per-trial bases and the clone mode (#1180), pushing only what delivery needs, recovery of a continue copy whose owner never returns and session bindings across machines (#1084), and pr_url (#1090).

Dependencies

Acceptance criteria

  • A step that continues a branch on which an earlier step committed .mcp.json, opencode.json or .tclaude/settings.json runs with the base version of each.
  • run.base.ref is the branch the member's checkout was on at workflow start, and a later base sent to next by another driver does not change it. A lease with an unparseable workspace fails its step.
  • With ready steps a: new and b: read-only, a driver declaring can.workspace: [read-only] gets b, and a stays pending.
  • In the example above, test runs in the change copy and fails, and the pass records the commit it read. A read-only step outside any copy still runs in the member's checkout.
  • A report whose changes.branch differs from the lease is rejected.
  • A prompt step in a new workspace records the worktree's team revision, and its tool does not start a background pull.
  • A run record keeps bad_session.

Verification

Focused workspace and local-driver tests for each criterion; build/typecheck/lint; one real-CLI run of the example where test sees the change, plus a later step that continues a branch with a committed .tclaude/settings.json. Update the bilingual workflow docs where behavior changes.

Activity

  1. self-assigned this
    on Oct 20, 2026
  2. SaulMoro commented on Oct 21, 2026

    @SaulMoro
    CollaboratorAuthor

    #1251 implements F1 to F7.

    These decisions go beyond the text above:

    • F4, what a reader records. A read-only step in a copy reports a new read field (branch, base, commit) instead of changes. report rejects read on any other lease, and rejects a reader's success without it.
    • F4, which branch a reader reads. It is the branch that the reader's declared needs wrote or read. When they touched none, it is the enclosing copy's branch. Needs on several branches fail with workspace_base and ask for a step that merges them.
    • F4, leaving the copy as found. A reader that changed the copy has the change kept on refs/teamai/kept/<attempt>. Its submodule commits go under refs/teamai/kept-modules/<attempt>/. Then the copy is reset to the commit it read.
      • Nothing that could not be kept is reset. That covers dirty submodule files, a commit made only in a nested submodule, and a removed submodule.
      • A teamai-unrestored marker is written when the reader starts. It refuses later steps in that copy until the copy is back, even after a killed driver.
    • F1 scope. Configured, non-default toolPaths are not reset, because each tool reads its own fixed project paths.

    The real-CLI record of the issue's example is in #1251. At 6529f53d the run fails with "feature.txt missing, the test ran without the change". With #1251 it succeeds, and change.implement sees the base .tclaude/settings.json.

    Open and not covered here: refs/teamai/kept* refs are never pruned. They are the recovery copy of a reader's change, and pruning them needs a run retention policy that does not exist yet.

    The writer side no longer depends on what the user's git config shows. commitWork always stages, and checks the index with --ignore-submodules=none --no-ext-diff. A retry's push check reads git branch --no-color.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions