From b61786ff30dab9b38d05ac7cf545cc91f400a90b Mon Sep 17 00:00:00 2001 From: "arc-1e68f1[bot]" <265161896+arc-1e68f1[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 17:40:51 -0400 Subject: [PATCH] Rename metadata branch from `refs/fabro/{run_id}` to `fabro/meta/{run_id}` (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fabro(01KKS6PG9929P116A2RRKXC738): toolchain (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 2 Fabro-Checkpoint: a274eab045a7da6342ecf8659f26a70f1c03add2 ⚒️ Generated with [Fabro](https://fabro.sh) * fabro(01KKS6PG9929P116A2RRKXC738): preflight_compile (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 3 Fabro-Checkpoint: 8ce709ecdb9b1884e2811af7b4e34db14a21466e ⚒️ Generated with [Fabro](https://fabro.sh) * fabro(01KKS6PG9929P116A2RRKXC738): preflight_lint (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 4 Fabro-Checkpoint: 4f4c237214b33764e56dac738ee24d4709db0bbc ⚒️ Generated with [Fabro](https://fabro.sh) * fabro(01KKS6PG9929P116A2RRKXC738): implement (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 5 Fabro-Checkpoint: baf4d48a0f37e14ad3c09684e045546225e03f02 ⚒️ Generated with [Fabro](https://fabro.sh) * fabro(01KKS6PG9929P116A2RRKXC738): simplify (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 6 Fabro-Checkpoint: ddc0967c0b86be389ac906ddd22b2c87d60911d6 ⚒️ Generated with [Fabro](https://fabro.sh) * fabro(01KKS6PG9929P116A2RRKXC738): verify (success) Fabro-Run: 01KKS6PG9929P116A2RRKXC738 Fabro-Completed: 7 Fabro-Checkpoint: 2249924bb2477b578c7a0691eaa7c9f1f8f455a2 ⚒️ Generated with [Fabro](https://fabro.sh) --------- Co-authored-by: Fabro Co-authored-by: Bryan Helmkamp Co-authored-by: Claude Opus 4.6 (1M context) --- docs/agents/outputs.mdx | 4 ++-- docs/execution/checkpoints.mdx | 17 +++++++---------- lib/crates/fabro-workflows/src/cli/rewind.rs | 16 ++++++++++++++-- lib/crates/fabro-workflows/src/cli/run.rs | 7 ++----- lib/crates/fabro-workflows/src/engine.rs | 14 +++----------- lib/crates/fabro-workflows/src/git.rs | 19 ++++++++++++++++--- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/docs/agents/outputs.mdx b/docs/agents/outputs.mdx index 9e7e7ae29..978f2e910 100644 --- a/docs/agents/outputs.mdx +++ b/docs/agents/outputs.mdx @@ -182,7 +182,7 @@ This keeps preambles concise while still giving agents a path to read the full o Artifact data is persisted on the Git [metadata branch](/execution/checkpoints#metadata-branch) alongside checkpoint data. Each time a checkpoint is written, any file-backed artifacts are included as additional entries: ``` -refs/fabro/{run_id} +fabro/meta/{run_id} manifest.json graph.fabro checkpoint.json @@ -291,4 +291,4 @@ Outputs and artifacts appear in several observability surfaces: | `WorkflowRunCompleted` event | `artifact_count` -- total number of offloaded artifacts across the run | | [Retros](/execution/retros) | Per-stage `files_touched` and aggregate `files_touched` across all stages | | [Preambles](/execution/context#preamble-construction) | File list and artifact pointer references for completed stages | -| Stage logs | `status.json` in each stage's run directory contains the full outcome including `files_touched` | +| Stage logs | `status.json` in each stage's run directory contains the full outcome including `files_touched` | \ No newline at end of file diff --git a/docs/execution/checkpoints.mdx b/docs/execution/checkpoints.mdx index 95811fbeb..a2968534f 100644 --- a/docs/execution/checkpoints.mdx +++ b/docs/execution/checkpoints.mdx @@ -12,7 +12,7 @@ Each run creates two Git branches that work in tandem: | Branch | Ref format | Contains | |---|---|---| | **Run branch** | `fabro/run/{run_id}` | File changes made by agents and commands — the actual work product | -| **Metadata branch** | `refs/fabro/{run_id}` | Checkpoint JSON, the workflow graph, a run manifest, and offloaded artifacts | +| **Metadata branch** | `fabro/meta/{run_id}` | Checkpoint JSON, the workflow graph, a run manifest, and offloaded artifacts | The run branch is a regular Git branch that grows one commit per completed node. The metadata branch is an orphan branch (no shared history with your code) that stores structured data using Git's object database directly — no working tree needed. @@ -41,7 +41,7 @@ The `Fabro-Checkpoint` trailer links each run branch commit to its metadata bran ### Metadata branch -The metadata branch (`refs/fabro/{run_id}`) is an orphan branch that stores structured run data using Git's object storage directly (via `git2`). It is initialized at run start with: +The metadata branch (`fabro/meta/{run_id}`) is an orphan branch that stores structured run data using Git's object storage directly (via `git2`). It is initialized at run start with: - **`manifest.json`** — Run metadata: run ID, graph name, node/edge counts, base SHA, and branch name - **`graph.fabro`** — The workflow DOT source as it was parsed @@ -87,7 +87,7 @@ This means your original working directory stays untouched while the agent makes If the working directory has uncommitted changes, Fabro skips worktree setup and runs in place, logging a warning. Git checkpointing is disabled in this case. -For Daytona sandboxes, the worktree is created inside the remote sandbox instead. The metadata branch is still written to the host repository so that runs can be resumed locally. Both the run branch and the metadata branch are pushed to origin after each checkpoint — the run branch is pushed from the sandbox, while the metadata branch is pushed from the host using a GitHub App installation token. On the remote, the metadata branch appears at `fabro/meta/{run_id}` (rather than the local `refs/fabro/{run_id}` custom ref, since GitHub disallows branch names starting with `refs/`). +For Daytona sandboxes, the worktree is created inside the remote sandbox instead. The metadata branch is still written to the host repository so that runs can be resumed locally. Both the run branch and the metadata branch are pushed to origin after each checkpoint — the run branch is pushed from the sandbox, while the metadata branch is pushed from the host using a GitHub App installation token. ## Resuming a run @@ -111,7 +111,7 @@ Resume from the Git branches created during a previous run: fabro run --run-branch fabro/run/01JKXYZ... ``` -This reads the checkpoint, manifest, and graph DOT from the metadata branch (`refs/fabro/01JKXYZ...`), re-attaches a worktree to the existing run branch, and resumes execution. No workflow file argument is needed — everything is recovered from Git. +This reads the checkpoint, manifest, and graph DOT from the metadata branch (`fabro/meta/01JKXYZ...`), re-attaches a worktree to the existing run branch, and resumes execution. No workflow file argument is needed — everything is recovered from Git. 1. Fabro reads `checkpoint.json` from the metadata branch @@ -147,11 +147,8 @@ git show fabro/run/01JKXYZ... # Diff the full run against the starting point git diff main..fabro/run/01JKXYZ... -# Read checkpoint data from the metadata branch (local) -git show refs/fabro/01JKXYZ...:checkpoint.json | jq .current_node - -# Read checkpoint data from the remote (Daytona runs) -git show origin/fabro/meta/01JKXYZ...:checkpoint.json | jq .current_node +# Read checkpoint data from the metadata branch +git show fabro/meta/01JKXYZ...:checkpoint.json | jq .current_node ``` ## Rewinding to an earlier checkpoint @@ -182,4 +179,4 @@ It is skipped when: - The working directory has uncommitted changes - The working directory is not a Git repository -- The run uses `--dry-run` +- The run uses `--dry-run` \ No newline at end of file diff --git a/lib/crates/fabro-workflows/src/cli/rewind.rs b/lib/crates/fabro-workflows/src/cli/rewind.rs index 1997cb00e..44cc027e9 100644 --- a/lib/crates/fabro-workflows/src/cli/rewind.rs +++ b/lib/crates/fabro-workflows/src/cli/rewind.rs @@ -394,7 +394,7 @@ pub fn execute_rewind( } // Force-push metadata branch - let meta_refspec = format!("+refs/heads/{meta_branch}:refs/heads/fabro/meta/{run_id}"); + let meta_refspec = format!("+refs/heads/{meta_branch}:refs/heads/{meta_branch}"); crate::git::push_branch(repo_path, "origin", &meta_refspec) .map_err(|e| anyhow::anyhow!("failed to push metadata branch: {e}"))?; @@ -408,7 +408,7 @@ pub fn execute_rewind( /// Find a run ID by exact match or unambiguous prefix. pub fn find_run_id_by_prefix(repo: &Repository, prefix: &str) -> Result { let refs = repo.references()?; - let pattern = "refs/heads/refs/fabro/"; + let pattern = "refs/heads/fabro/meta/"; let mut matches = Vec::new(); for reference in refs.flatten() { @@ -951,4 +951,16 @@ mod tests { assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("no run found")); } + + #[test] + fn rewind_push_refspec_uses_same_name_on_both_sides() { + // The meta branch name should work directly as a refspec + // without needing strip_prefix translation + let meta_branch = MetadataStore::branch_name("run-1"); + let refspec = format!("+refs/heads/{meta_branch}:refs/heads/{meta_branch}"); + assert_eq!( + refspec, + "+refs/heads/fabro/meta/run-1:refs/heads/fabro/meta/run-1" + ); + } } diff --git a/lib/crates/fabro-workflows/src/cli/run.rs b/lib/crates/fabro-workflows/src/cli/run.rs index b60ea79da..8051c91ea 100644 --- a/lib/crates/fabro-workflows/src/cli/run.rs +++ b/lib/crates/fabro-workflows/src/cli/run.rs @@ -1618,7 +1618,7 @@ async fn setup_remote_git( /// Resume a workflow run from a git run branch. /// /// Reads the checkpoint, manifest, and graph DOT from the metadata branch -/// (`refs/fabro/{run_id}`), re-attaches a worktree to the existing run branch, +/// (`fabro/meta/{run_id}`), re-attaches a worktree to the existing run branch, /// and resumes execution via `run_from_checkpoint()`. async fn run_from_branch( args: RunArgs, @@ -2311,10 +2311,7 @@ async fn write_finalize_commit(config: &RunConfig, run_dir: &std::path::Path) { } // Push the finalize commit - let run_id_part = meta_branch - .strip_prefix("refs/fabro/") - .unwrap_or(meta_branch); - let refspec = format!("{meta_branch}:refs/heads/fabro/meta/{run_id_part}"); + let refspec = format!("refs/heads/{meta_branch}"); crate::engine::git_push_host(repo_path, &refspec, &config.github_app, "finalize metadata") .await; } diff --git a/lib/crates/fabro-workflows/src/engine.rs b/lib/crates/fabro-workflows/src/engine.rs index d52ba815c..47e7da216 100644 --- a/lib/crates/fabro-workflows/src/engine.rs +++ b/lib/crates/fabro-workflows/src/engine.rs @@ -801,7 +801,7 @@ pub struct RunConfig { pub base_sha: Option, /// Git branch name for the run (e.g. `fabro/run/{run_id}`). pub run_branch: Option, - /// Metadata branch name for git-native checkpoint storage (e.g. `refs/fabro/{run_id}`). + /// Metadata branch name for git-native checkpoint storage (e.g. `fabro/meta/{run_id}`). pub meta_branch: Option, /// User-defined key-value labels for this run. pub labels: HashMap, @@ -1987,15 +1987,7 @@ impl WorkflowRunEngine { if let (Some(ref meta_branch), Some(ref repo_path)) = (&config.meta_branch, &config.host_repo_path) { - // The metadata branch is stored locally as a custom ref - // (e.g. refs/fabro/{run_id}). Push it to a normal branch on - // the remote since GitHub rejects branch names starting - // with "refs/". - let run_id_part = meta_branch - .strip_prefix("refs/fabro/") - .unwrap_or(meta_branch); - let refspec = - format!("{meta_branch}:refs/heads/fabro/meta/{run_id_part}"); + let refspec = format!("refs/heads/{meta_branch}"); let meta_push_ok = git_push_host( repo_path, &refspec, @@ -2004,7 +1996,7 @@ impl WorkflowRunEngine { ) .await; self.services.emitter.emit(&WorkflowRunEvent::GitPush { - branch: format!("fabro/meta/{run_id_part}"), + branch: meta_branch.clone(), success: meta_push_ok, }); } diff --git a/lib/crates/fabro-workflows/src/git.rs b/lib/crates/fabro-workflows/src/git.rs index 562e7b348..d2b16cd5d 100644 --- a/lib/crates/fabro-workflows/src/git.rs +++ b/lib/crates/fabro-workflows/src/git.rs @@ -11,6 +11,9 @@ use crate::error::{FabroError, Result}; /// Branch prefix for workflow run branches (e.g. `fabro/run/{run_id}`). pub const RUN_BRANCH_PREFIX: &str = "fabro/run/"; +/// Branch prefix for metadata branches (e.g. `fabro/meta/{run_id}`). +pub const META_BRANCH_PREFIX: &str = "fabro/meta/"; + /// Resolved git author identity for checkpoint commits. #[derive(Debug, Clone, PartialEq)] pub struct GitAuthor { @@ -343,7 +346,7 @@ pub fn scan_node_files(run_dir: &Path) -> Vec<(String, Vec)> { /// Git-native metadata storage for pipeline runs. /// /// Stores checkpoint data, manifests, and graph DOT on an orphan branch -/// (`fabro/{run_id}`) so that runs can be resumed from git alone. +/// (`fabro/meta/{run_id}`) so that runs can be resumed from git alone. pub struct MetadataStore { repo_path: std::path::PathBuf, author: GitAuthor, @@ -357,9 +360,9 @@ impl MetadataStore { } } - /// Returns the branch ref name for a run: `refs/fabro/{run_id}`. + /// Returns the branch name for a run: `fabro/meta/{run_id}`. pub fn branch_name(run_id: &str) -> String { - format!("refs/fabro/{run_id}") + format!("{META_BRANCH_PREFIX}{run_id}") } /// Format a commit message with the standard Fabro footer appended. @@ -1218,4 +1221,14 @@ mod tests { let err = ensure_clean_and_pushed(&repo_dir, "origin", None).unwrap_err(); assert!(err.to_string().contains("detached HEAD")); } + + #[test] + fn metadata_branch_name_uses_meta_prefix() { + assert_eq!(MetadataStore::branch_name("abc-123"), "fabro/meta/abc-123"); + } + + #[test] + fn meta_branch_prefix_constant() { + assert!(MetadataStore::branch_name("x").starts_with(META_BRANCH_PREFIX)); + } }