From b413b9b29ca07d0a56b8967ed076598bf24ed0d3 Mon Sep 17 00:00:00 2001 From: Scott Werner Date: Fri, 4 Sep 2026 16:58:30 -0400 Subject: [PATCH] Inherit the parent branch, not its pinned commit, for child run targets A child created without a target copied the parent's full Git target, including the sha admitted for the parent. Clone-based providers never fall back to branch HEAD, so a child created after the parent pushed new commits was checked out at the parent's starting commit and never saw the work it was meant to review or continue. Inherit the repository and branch only, so the child resolves the branch's current remote HEAD at admission; the parent's pinned commit and tag stay on the parent. Callers that want a pinned child pass an explicit target. Folder and none targets are unchanged. Co-Authored-By: Claude Fable 5.1 --- docs/public/agents/mcp.mdx | 2 +- docs/public/execution/child-runs.mdx | 2 +- lib/apps/fabro-server/src/run_tool_create.rs | 35 ++++++++++++++++++-- lib/components/fabro-tool/src/create.rs | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/public/agents/mcp.mdx b/docs/public/agents/mcp.mdx index f1f5ce95c..0bf9a0921 100644 --- a/docs/public/agents/mcp.mdx +++ b/docs/public/agents/mcp.mdx @@ -153,7 +153,7 @@ You can also reuse an exact immutable workflow version without uploading content } ``` -Workflow source and run target are independent. Object-form calls can provide an explicit Git, `none`, or folder target. A workflow agent inherits its parent run's canonical target when `target` is omitted. Folder targets are available only to standalone MCP and Local workflow agents with a shared host filesystem; Docker and Daytona parents cannot select a server-host folder, even by naming a Local child environment. When `target` is omitted, standalone MCP derives it from the selected environment the way `fabro run` does: a Local environment targets the working directory as a folder, and a Docker or Daytona environment targets the attached GitHub checkout only when it can prove that the exact local HEAD is available from the canonical origin (a directory with no Git metadata runs with no workspace); otherwise, push the commit or provide an explicit target. Docker and Daytona agents must send inline files or a stored version ID—never send a sandbox path as a selector, because the run worker cannot use that path to read the sandbox's files. +Workflow source and run target are independent. Object-form calls can provide an explicit Git, `none`, or folder target. A workflow agent inherits its parent run's canonical target when `target` is omitted; for a Git parent that means the repository and branch at their current remote HEAD, not the parent's pinned commit, so a child sees commits the parent has pushed. Folder targets are available only to standalone MCP and Local workflow agents with a shared host filesystem; Docker and Daytona parents cannot select a server-host folder, even by naming a Local child environment. When `target` is omitted, standalone MCP derives it from the selected environment the way `fabro run` does: a Local environment targets the working directory as a folder, and a Docker or Daytona environment targets the attached GitHub checkout only when it can prove that the exact local HEAD is available from the canonical origin (a directory with no Git metadata runs with no workspace); otherwise, push the commit or provide an explicit target. Docker and Daytona agents must send inline files or a stored version ID—never send a sandbox path as a selector, because the run worker cannot use that path to read the sandbox's files. Use `goal` for inline goal text or `goal_file` to read the run goal from a file. They are mutually exclusive. Relative `goal_file` paths resolve from the run's `cwd`, or from the MCP server working directory when `cwd` is omitted. Like selectors, `goal_file` requires a native/shared filesystem; Docker and Daytona agents must send `goal` text by value. diff --git a/docs/public/execution/child-runs.mdx b/docs/public/execution/child-runs.mdx index 09bcae97a..0158c8469 100644 --- a/docs/public/execution/child-runs.mdx +++ b/docs/public/execution/child-runs.mdx @@ -134,7 +134,7 @@ Reuse content already registered with Fabro by supplying its exact immutable ID: } ``` -Workflow content and workspace target are separate choices. If `target` is omitted, a child inherits the parent's full canonical Git, `none`, or folder target. An explicit Git, `none`, or folder target overrides that inheritance while the current run remains the forced parent. Folder targets require a Local parent with a shared host filesystem; Docker and Daytona parents cannot select a server-host folder, even by naming a Local child environment. +Workflow content and workspace target are separate choices. If `target` is omitted, a child inherits the parent's canonical target: a `none` or folder target as-is, and a Git target's repository and branch. The child checks out the branch's current remote HEAD, so commits the parent has pushed are visible to it; the parent's pinned commit and tag are not carried over. Pass an explicit Git target with a `sha` to pin a child. An explicit Git, `none`, or folder target overrides that inheritance while the current run remains the forced parent. Folder targets require a Local parent with a shared host filesystem; Docker and Daytona parents cannot select a server-host folder, even by naming a Local child environment. `goal_file` is also a shared-filesystem feature. Local agents can read it relative to the operation `cwd`; Docker and Daytona agents must send the resolved `goal` text by value. diff --git a/lib/apps/fabro-server/src/run_tool_create.rs b/lib/apps/fabro-server/src/run_tool_create.rs index 71501d808..e5ffcefdd 100644 --- a/lib/apps/fabro-server/src/run_tool_create.rs +++ b/lib/apps/fabro-server/src/run_tool_create.rs @@ -123,7 +123,7 @@ impl ServerRunCreateAdapter { inherited_target: Some(target), .. } => Ok(ResolvedTarget { - target: target.clone(), + target: inherit_parent_target(target), warnings: Vec::new(), }), RunCreateMode::Worker { @@ -263,6 +263,25 @@ struct ResolvedTarget { warnings: Vec, } +/// The target a child inherits when it omits its own. A parent's Git target +/// is pinned to the commit admitted for the parent, and clone-based providers +/// never fall back to branch HEAD, so carrying that pin forward would hide +/// every commit the parent has since pushed from a child meant to review or +/// continue that work. The child follows the parent's branch instead; the +/// pinned commit and tag stay on the parent only. Folder and none targets are +/// inherited as-is. +fn inherit_parent_target(parent: &RunTarget) -> RunTarget { + match parent { + RunTarget::Git(git) => RunTarget::Git(fabro_types::GitRunTarget { + repo: git.repo.clone(), + branch: git.branch.clone(), + tag: None, + sha: None, + }), + RunTarget::None {} | RunTarget::Folder { .. } => parent.clone(), + } +} + /// Collect an inline workflow from its supplied bytes. The entrypoint is an /// exact key of the file map, never a checkout selector. async fn collect_inline_workflow( @@ -499,7 +518,7 @@ mod tests { } #[tokio::test] - async fn workflow_version_stored_create_skips_registration_and_inherits_exact_worker_target() { + async fn workflow_version_stored_create_skips_registration_and_inherits_worker_branch() { let client = no_proxy_client("http://127.0.0.1:9"); let workflow_version_id: WorkflowVersionId = fabro_types::BlobHash::new(b"stored").into(); let inherited = RunTarget::Git(GitRunTarget { @@ -526,7 +545,17 @@ mod tests { .unwrap(); assert_eq!(prepared.workflow_version_id, workflow_version_id); - assert_eq!(prepared.target, inherited); + // The child follows the parent's branch so commits the parent pushed + // are visible; the parent's pinned commit and tag are not inherited. + assert_eq!( + prepared.target, + RunTarget::Git(GitRunTarget { + repo: "fabro-sh/fabro".to_string(), + branch: "main".to_string(), + tag: None, + sha: None, + }) + ); } #[tokio::test] diff --git a/lib/components/fabro-tool/src/create.rs b/lib/components/fabro-tool/src/create.rs index bee90d63a..4409744dd 100644 --- a/lib/components/fabro-tool/src/create.rs +++ b/lib/components/fabro-tool/src/create.rs @@ -131,7 +131,7 @@ impl JsonSchema for CreateRunWorkflowSource { /// spelled out here and pinned by the serde parity test below. fn run_target_schema(_: &mut SchemaGenerator) -> Schema { json_schema!({ - "description": "Canonical run workspace target. Worker calls inherit the parent target when omitted; standalone calls derive it from the selected environment: Local environments target the working directory folder, and clone-based environments require an attached GitHub checkout whose exact local HEAD is available from the canonical origin. Folder targets require a standalone or Local-worker filesystem context.", + "description": "Canonical run workspace target. Worker calls inherit the parent target when omitted (a Git parent contributes its repository and branch, not its pinned commit or tag, so the child sees commits the parent has pushed); standalone calls derive it from the selected environment: Local environments target the working directory folder, and clone-based environments require an attached GitHub checkout whose exact local HEAD is available from the canonical origin. Folder targets require a standalone or Local-worker filesystem context.", "anyOf": [ { "type": "null" }, {