Configure implement-plan sandbox tooling (#377)

Fixes implement-plan runs failing at verify time when cloned sandboxes
lack Git identity, and prevents the verify forbidden-pattern scan from
being silently skipped when `rg` is unavailable.

## Changes
- Install `ripgrep` in the Fabro Daytona image and bump the snapshot ref
to `fabro-v12` so Daytona rebuilds it.
- Configure repository-local Git `user.name` and `user.email` from
`run.git.author` during workflow initialization before lifecycle setup
commands or workflow stages run.
- Make the implement-plan verify stage fail explicitly if `rg` is
missing.

## Validation
- `cargo test -p fabro-workflow
configure_sandbox_git_identity_uses_run_author --quiet`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-workflow --all-targets --
-D warnings`
- `cargo run -p fabro-cli -- validate
.fabro/workflows/implement-plan/workflow.fabro`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
This commit is contained in:
Bryan Helmkamp 2026-05-23 19:21:28 -04:00 • committed by GitHub
parent bad8c0baf5
commit 1c2abbb2f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 4 deletions

View file

@ -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/*

View file

@ -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]

View file

@ -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

View file

@ -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();