Extract shared SSH types (SshOutput, SshRunner, GitCloneParams) and
utility functions (wrap_bash_command, resolve_clone_url, clone_repo)
into a new ssh_common module, eliminating ~270 lines of duplication
between the exe and ssh sandbox implementations.
Also extract a shared resolve_path helper used by four sandbox
implementations, and fix an O(n log n) metadata syscall issue in
LocalSandbox::glob by switching to sort_by_cached_key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the Sandbox trait, types, and all sandbox implementations from
fabro-agent and four separate crates (fabro-exe, fabro-ssh, fabro-sprites,
fabro-daytona) into a single fabro-sandbox crate. This cleans up the
dependency graph — implementation crates no longer pull in the full
fabro-agent just for the trait.
The new crate uses feature flags (local, docker, ssh, exe, sprites,
daytona, test-support) to gate each implementation. The shell_quote()
helper is unified into a single shared implementation, eliminating four
duplicate copies.
fabro-agent now re-exports all sandbox types from fabro-sandbox for
backward compatibility. The four absorbed crates are removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AgentApiBackend::create_session() always used the backend's default
model/provider, ignoring attributes set on the node by stylesheet
application. The one_shot path already read node.model() correctly
but the agent session path (used by implement and other agent stages)
did not. Also fixes usage reporting and provider_used.json to reflect
the actual model used rather than the backend default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The release tarball nests the binary in a subdirectory
(fabro-{triple}/fabro), but the upgrade code expected it at the
tarball root. Use the correct nested path matching the tarball structure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The double-fork in spawn_detached_unix inherited unflushed stdout/stderr
buffers from the parent process. When the intermediate child called
std::process::exit(0), libc cleanup flushed these buffers again, causing
duplicate output that broke trycmd snapshot comparisons in release builds
(where telemetry defaults to enabled).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compute sanitize_command, repository_identifier, and CI check once
before the if/else branches to avoid duplicate git I/O
- Make should_track_for_level private (only used by _track_inner)
- Check tracks.is_empty() before credentials in upload_blocking for
consistency with emit()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract telemetry from fabro-util into a dedicated fabro-telemetry crate.
Replace the synchronous Telemetry struct with a global background buffer
that flushes periodically via blocking HTTP (mid-run) and detached
subprocess (final flush at exit). The new API is init_cli()/track!()/shutdown().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reference SEGMENT_BASE_URL (var) and SEGMENT_WRITE_KEY (secret) so
they are compiled into release binaries via option_env!().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store only the base URL (e.g. https://api.segment.io) so that
different endpoints (/v1/batch, /v1/track, etc.) can reuse it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow overriding the Segment API endpoint via the SEGMENT_API_URL
environment variable at build time, defaulting to the standard
https://api.segment.io/v1/batch endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DaytonaConfig struct had a skip_clone field that was missing from
both the OpenAPI spec and the conformance test initializer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
doctor and provider_auth used cheapest_model (gpt-5-mini) for connectivity
probes, but gpt-5-mini is rejected by the ChatGPT/Codex backend. Adds
probe_model_for_provider() which returns gpt-5.4-mini for OpenAI and falls
back to the default model for other providers.
Fixes#96
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gpt-5-mini is not supported on the ChatGPT/Codex backend, causing all
16 OpenAI parity tests to fail when using browser-auth credentials.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce an Env trait in fabro-util so tests can inject a HashMap-backed
TestEnv instead of mutating process-global environment variables, which
is unsafe since Rust 1.66+ and causes flakiness in concurrent tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two Daytona integration tests used std::env::set_current_dir to a temp
directory so detect_repo_info() would fail and skip cloning. Since cwd
is process-global, this poisoned concurrent tests. Replace with an
explicit skip_clone config flag that skips repo detection and cloning
during sandbox initialization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This function had zero callers after sync_status was introduced in
ad84f9f9. Remove it along with its four tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fully-qualified fabro_workflows::GitSyncStatus paths with a
use import, and consolidate the near-duplicate dirty-worktree warning arms
into a single block that varies only the environment name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The git sync check and auto-push logic was gated on should_create_worktree,
which was always false for remote sandboxes. This meant Daytona/Exe/SSH runs
silently proceeded without verifying commits were pushed or warning about
uncommitted changes. Replace the git_clean boolean and should_create_worktree
boolean with two enums (GitSyncStatus: Synced/Unsynced/Dirty and
WorkdirStrategy: LocalDirectory/LocalWorktree/Cloud) so every combination
is handled explicitly via match arms. Also display the base commit SHA for
cloud runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MDX treats {…} as JSX expressions. Bare curly braces in headings
and bold text caused acorn parse failures during Mintlify deploy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some providers return summaries of example.com without the exact phrases
"Example Domain" or "example.com", so accept related terms like
"documentation" or "iana" alongside "example".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zai provider tests are unreliable (editing, web_fetch, web_search
failures). Gate them behind cfg(feature = "quarantine") like Inception
tests so they don't block the default test suite.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `--` separator before `bash -c` in sprite exec args so the CLI
stops parsing flags and doesn't interpret `-c` as its own flag.
Also add E2E live test commands to CLAUDE.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The assertion was checking for the old repo name brynary/arc instead of
fabro-sh/fabro.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Docker and Daytona asset collection tests were failing because
asset_globs was empty, causing the engine to skip collection entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Codex endpoint commit (459a9c22) added `store: false` unconditionally
to all OpenAI Responses API requests. This broke multi-turn conversations
because OpenAI doesn't persist items when store is false, so referencing
previous reasoning/message IDs on subsequent turns returns a 404. The fix
makes store conditional: true for regular OpenAI (the API default), false
only for the Codex endpoint which requires it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sets up apps/remotion with a 5-second 1080p intro video featuring the
Fabro symbol, logotype, and tagline animated over the brand navy background.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fabro-api doesn't use `SandboxProvider::Exe` but was unconditionally
enabling `exedev` on fabro-workflows. Cargo feature unification made
the `Exe` variant exist while fabro-cli's cfg-gated match arms were
inactive, causing non-exhaustive pattern errors in workspace builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Disable git2 default features (`ssh`, `https`) which pulled in
`openssl-sys` and `libssh2-sys`
- These transports are unused — all git2 usage in the codebase is local
repo operations (commits, blobs, revwalks)
- The CLI binary no longer dynamically links against `libssl.3.dylib` /
`libcrypto.3.dylib`
Fixes#92
## Verification
- `otool -L target/debug/fabro | grep ssl` returns nothing (no OpenSSL
linkage)
- `cargo tree -i openssl-sys` returns nothing (fully removed from dep
tree)
- All 179 workspace tests pass
## Test plan
- [ ] Build release binary and verify with `otool -L` (macOS) or `ldd`
(Linux) that no OpenSSL refs remain
- [ ] Run on a machine without OpenSSL v3 installed — should launch
without `dyld` error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract git_repo_root() helper in init.rs (was duplicated between
run_init and run_deinit)
- Fix TOCTOU in run_deinit: remove .exists() check, handle NotFound
from remove_file directly
- Change dotenv::remove_env_key() to return Option<String> so callers
don't need to separately parse the file to check key existence
- Remove merge_env wrapper in install.rs, call shared function directly
- Remove duplicate merge_env tests from install.rs (already in dotenv.rs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>