diff --git a/.fabro/Dockerfile b/.fabro/Dockerfile index f523c9c99..6268a5484 100644 --- a/.fabro/Dockerfile +++ b/.fabro/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get install -y --no-install-recommends \ - curl git ca-certificates build-essential pkg-config libssl-dev unzip python3 \ + curl git ripgrep ca-certificates build-essential pkg-config libssl-dev unzip python3 \ xvfb xfce4 xfce4-terminal x11vnc novnc dbus-x11 \ libx11-6 libxrandr2 libxext6 libxrender1 libxfixes3 libxss1 libxtst6 libxi6 \ && rm -rf /var/lib/apt/lists/* diff --git a/.fabro/project.toml b/.fabro/project.toml index 18b684a79..c6710f052 100644 --- a/.fabro/project.toml +++ b/.fabro/project.toml @@ -17,7 +17,7 @@ auto_stop = "30m" repo = "fabro-sh/fabro" [environments.fabro-dev.image] -ref = "fabro-v11" +ref = "fabro-v12" dockerfile = { path = "Dockerfile" } [environments.fabro-dev.resources] diff --git a/.fabro/workflows/implement-plan/workflow.fabro b/.fabro/workflows/implement-plan/workflow.fabro index bfa79d6c2..d4d99bf9d 100644 --- a/.fabro/workflows/implement-plan/workflow.fabro +++ b/.fabro/workflows/implement-plan/workflow.fabro @@ -17,7 +17,7 @@ digraph ImplementPlan { implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"] simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"] simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"] - verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"] + verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"] fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3] start -> toolchain diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs index 3165f7855..c620c0151 100644 --- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs @@ -12,7 +12,7 @@ use fabro_hooks::{HookContext, HookDecision, HookEvent, HookExecutionContext, Ho use fabro_model::Catalog; use fabro_sandbox::{ GitSetupIntent, ReadBeforeWriteSandbox, SandboxEventCallback, SandboxSpec, - reconnect_for_run_with_callback, + reconnect_for_run_with_callback, shell_quote, }; use fabro_static::EnvVars; use fabro_vault::Vault; @@ -27,6 +27,7 @@ use super::types::{InitOptions, Initialized, LlmSpec, Persisted, SandboxEnvSpec} use crate::devcontainer_bridge::{devcontainer_to_snapshot_config, run_devcontainer_lifecycle}; use crate::error::Error; use crate::event::{Event, RunNoticeCode, RunNoticeLevel}; +use crate::git::GitAuthor; use crate::github_token_source::{AppIatMinter, GitHubTokenSource}; use crate::handler::llm::{AgentAcpBackend, AgentApiBackend, BackendRouter, routing}; use crate::handler::{HandlerRegistry, default_registry}; @@ -66,6 +67,25 @@ fn git_setup_intent(run_options: &RunOptions) -> GitSetupIntent { } } +async fn configure_sandbox_git_identity( + sandbox: &dyn Sandbox, + author: &GitAuthor, +) -> Result<(), Error> { + let command = format!( + "git config --local user.name {} && git config --local user.email {}", + shell_quote(&author.name), + shell_quote(&author.email) + ); + sandbox + .exec_command(&command, 10_000, None, None, None) + .await + .map_err(|err| Error::engine_with_source("Sandbox git identity setup failed", err))? + .into_result("git config user identity") + .map_err(|err| Error::engine_with_source("Sandbox git identity setup failed", err))?; + + Ok(()) +} + fn build_sandbox_env( spec: &SandboxEnvSpec, github_app: Option<&fabro_github::GitHubCredentials>, @@ -574,6 +594,10 @@ pub async fn initialize( } } } + if sandbox.origin_url().is_some() { + let git_author = options.run_options.git_author(); + configure_sandbox_git_identity(sandbox.as_ref(), &git_author).await?; + } if !options.lifecycle.setup_commands.is_empty() { options.emitter.emit(&Event::SetupStarted { @@ -915,6 +939,29 @@ mod tests { (result, events) } + #[tokio::test] + async fn configure_sandbox_git_identity_uses_run_author() { + let sandbox = fabro_sandbox::test_support::MockSandbox::linux(); + let author = GitAuthor::from_options( + Some("Fabro Bot".to_string()), + Some("fabro-bot@example.com".to_string()), + ); + + configure_sandbox_git_identity(&sandbox, &author) + .await + .expect("git identity should configure"); + + let commands = sandbox + .captured_commands + .lock() + .expect("captured_commands lock poisoned") + .clone(); + assert_eq!(commands, vec![ + "git config --local user.name 'Fabro Bot' && git config --local user.email \ + fabro-bot@example.com" + ]); + } + #[tokio::test] async fn initialize_prepares_sandbox_and_uses_persisted_run_dir() { let temp = tempfile::tempdir().unwrap();