The fake /bin/sh script in the render_error protocol test printed and
exited without reading stdin, which raced the parent's write_all on
Linux — EPIPE would surface as ChildCrashed (500) instead of the
RenderFailed path (400) the test asserts. macOS pipe buffering masked
the race. Adding `cat >/dev/null` mirrors the sibling
protocol_violation test and makes the child consume the DOT input
before printing the RENDER_ERROR line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- rust.yml: move clippy to nightly-2026-04-14 (was stable); also pin
fmt to the same nightly date for consistency. Both jobs now use the
dated nightly and the run-step uses `cargo +nightly-2026-04-14 ...`.
- AGENTS.md: update developer commands to match CI.
- Duration constructors: replace `Duration::from_secs(N * 60)` /
`Duration::from_millis(N * 1000)` with `from_mins` / `from_secs` /
`from_hours` across the workspace to satisfy clippy's new
`duration_suboptimal_units` lint. std::time::Duration only — custom
`settings::duration::Duration` sites kept on `from_secs`.
- map/unwrap_or cleanup: `.map(f).unwrap_or(v)` → `.map_or(v, f)`,
`.map(f).unwrap_or(false)` on Result → `.is_ok_and(f)`, per
`clippy::map_unwrap_or`.
- Misc lints: collapse nested `if` into match guard in
handler/llm/api.rs and run_state.rs; replace `columns.len() > 0`
with `!columns.is_empty()`; switch a pair of `sort_by` calls to
`sort_by_key`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- serve.rs: annotate debug-only `bun run dev` spawn with
#[expect(clippy::disallowed_methods, ...)] and add the missing
watch_web field to three ServeArgs test fixtures.
- install.rs: replace absolute `fabro_server::serve::DEFAULT_TCP_PORT`
path with `serve::DEFAULT_TCP_PORT` (use is already imported) to
satisfy clippy::absolute_paths.
- pagination test: request an explicit page[limit]=100 for the
"fits in one page" case instead of relying on the server default,
so the test stays robust as the built-in model catalog grows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spawns `bun run dev` in apps/fabro-web as a child of the server process,
so a single command starts both the API server and the web asset watcher.
The flag is gated behind #[cfg(debug_assertions)] and does not exist in
release builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update 5 more dry_run_examples snapshots to use [GRAPH_PATH] filter
- Skip LLM preflight check when graph has no LLM nodes (fixes
preflight_allows_pull_request_enabled_without_github_credentials)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove EnvGuard and env-mutating test from fabro-auth (shared mutable state)
- Revert project.toml preserve=true that broke sandbox cleanup event tests
- Fix clippy: use is_some_and, scoped imports for StageStatus and render
- Update snapshot tests for new Run: ULID line and model_test output
- Fix preflight test assertion (name said "allows", asserted failure)
- Update cancel_queued_run test: cancelled runs now appear on board
- Add graph direction query param support to get_graph endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The stages API used checkpoint.current_node to identify the running
stage, but current_node is the last *completed* node — always already
in completed_nodes, so the running-stage check was always false.
Switch to checkpoint.next_node_id which correctly identifies the
currently-executing stage.
Also move SSE subscription from run-detail parent layout into the
StageSidebar component with since_seq=1 to replay all events and
close the race between loader fetch and SSE connection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix run detail status: display actual API status (submitted, running,
succeeded, failed, etc.) instead of always showing "Working"
- Implement /runs/{id}/stages endpoint in non-demo mode, reading from
checkpoint + events to build stage list with statuses and durations
- Fix /runs/{id}/graph to fall through to durable store when run is not
in the live map
- Render real workflow graph SVG on overview and graph pages instead of
hardcoded demo graph; remove unused DotDiagram component from overview
- Add dark mode CSS overrides for server-rendered SVG graphs
- Wire stage detail page to real event data: fetch from /events, filter
by node_id, and render as system/assistant/tool blocks
- Fix stage page 500: use apiJsonOrNull for unimplemented /turns endpoint
- Filter start/exit graph control nodes from stage lists in the UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarifies that Submitted/Starting runs are initializing, not just
pending. Also refactors run-detail to display the actual run status
via runStatusDisplay instead of mapping to board columns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Daytona SDK client was created via Client::new() which only reads
DAYTONA_API_KEY from process env vars. When the key is stored in the
fabro vault (via `fabro secret set`), it was never forwarded to the SDK,
causing "api_key or jwt_token must be provided" errors.
Thread the API key from the vault through SandboxSpec, DaytonaSandbox,
and reconnect paths so the SDK receives it via new_with_config().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Features like session_sandboxes and retros are server-level capability
flags, not user settings. Expose them on GET /system/info where they
belong alongside other server metadata.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /boards/runs endpoint was driven by the in-memory state.runs map,
which is empty after server restart. Now reads from SlateDB store so
runs persist across restarts.
Also makes board columns dynamic from the API response instead of
hardcoded in the frontend. Real mode returns: pending, running, waiting,
succeeded, failed. Demo mode returns: working, pending, review, merge.
Board layout changed from fixed 3-column grid to horizontal scroll.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Derive configured providers from env and vault when choosing default
models during run creation and materialization, and thread the resolved
run provider through execution handlers instead of recomputing it.
Also return a user-facing error when fabro-agent cannot infer a default
model for the selected provider.
When `disk_cache = true` in `[server.slatedb]`, Fabro enables SlateDB's
object-store cache at `<storage_root>/cache/slatedb`, caching raw S3
bytes on local disk to reduce read latency. All cache parameters use
SlateDB defaults (16 GB max, 4 MB parts). A warning is emitted if
enabled with `provider = "local"` since the cache adds overhead when
the object store is already on the local filesystem.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`fabro secret set` now supports three ways to provide the value: as a
positional arg (existing), piped via --value-stdin, or interactively
when stdin is a TTY (obscured with dialoguer::Password). Diagnostics
remediation messages drop the <value> placeholder to encourage
interactive input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three login paths (root redirect, dev token, GitHub OAuth callback)
now send users to /runs on first visit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the internal positional AppState builder with an AppStateConfig
and route both production and test setup through the new config-backed
path. Preserve the in-process test helper behavior while fixing the
ignored max_concurrent_runs argument with a regression test.
build_app_state_with_path derived the server.env path from the vault
path's parent directory, causing it to look in vaults/default/ instead
of the storage root. This made fabro doctor report missing GitHub App
credentials even though fabro install saved them correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server used option_env!() for FABRO_GIT_SHA and FABRO_BUILD_DATE,
but no build.rs set them — so `fabro version` always showed "unknown".
Add a build.rs to fabro-server (matching fabro-cli's) and remove the
Sandbox line from `fabro system info`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep the render-graph CLI integration test explicitly documented for
synchronous stdio subprocess usage, and make the garbage-stdout server
test drain stdin before returning invalid output so the Linux test path
stays deterministic.
Run Graphviz through an internal fabro subprocess so renderer failures no
longer share process fate with the server. Keep expected DOT parse failures
on the 400 path via an explicit stdout protocol, and treat child crashes or
protocol violations as 500s.
Add a server-targeted `fabro version` command for checking client and
server build identity without reading local storage directly.
This also removes version data from `/health`, moves doctor parity checks
to diagnostics, and updates the API spec, docs, generated client, and
coverage for the new contract.
Three issues prevented the vendored Graphviz C source from working on
Linux:
1. Missing _GNU_SOURCE: with -std=c11, strdup is not declared on
glibc. The compiler assumes it returns int, truncating the 64-bit
return value on aarch64 and causing a SIGSEGV in gvplugin_install.
2. Circular static library dependency: common/emit.c references
gvevent symbols from gvc, but gvc depends on common. The Linux
single-pass linker cannot resolve this cycle. Fixed by merging all
archives into one combined archive using GNU ar's MRI script mode.
3. HAVE_MEMRCHR: with _GNU_SOURCE, glibc declares memrchr, which
conflicts with Graphviz's own static definition. Fixed by defining
HAVE_MEMRCHR on Linux to use the glibc declaration instead.
Also fixes: clippy borrow_as_ptr warning, disallowed_methods in
build.rs, and resolves a pre-existing merge conflict in serve.rs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Doctor warnings for Sandbox and Brave Search now display the exact
command needed to configure the secret. Backtick-delimited text in
remediation strings renders in bold cyan, matching the conventional
CLI command styling used elsewhere.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sandbox shows "recommended, not configured" and Brave Search is
renamed to "Web Search (Brave)" with "optional, not configured".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the server startup path that inferred dry-run from provider
availability and let run.execution.mode inherit normally from
settings.
Model tests now return skip for unconfigured providers at request
time, completions use the real error path, and the CLI/docs/tests are
updated for the removed server --dry-run flag.
Now that Graphviz is vendored, remove the DepSpec/probe_system_deps/
check_system_deps infrastructure from doctor.rs (empty since the
vendoring), the no-op pre-flight check from install.rs, and the
stale hardcoded "dot" check from demo diagnostics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the Command::new("dot") shell-out in render_dot() with a direct
FFI call to the vendored Graphviz library. Drop PNG support (SVG only).
Remove GraphFormat enum, dot_is_available() helpers, dot-related
diagnostics/doctor checks, and the graphviz install prompt. Update
OpenAPI spec to remove png format and 502 responses. Update CLI help
text, snapshot tests, and documentation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use short imports instead of absolute paths in test assertions
(fabro-config merge.rs, resolve/mod.rs)
- Remove needless raw string hashes where string body has no quotes
(fabro-config, fabro-workflow, fabro-server)
- Use struct initializer instead of field reassignment on Default
(fabro-types resolved.rs)
- Allow disallowed_methods for Command::new in test that exercises
a real login command (fabro-auth resolve.rs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The fabro-http crate's proxy policy mechanism was not being used in
tests. http_api.rs used #[cfg(test)] to call .no_proxy(), but cfg(test)
only applies within the crate being tested — downstream crates like
fabro-workflow and fabro-cli hit the production path with system proxy
discovery, adding ~900ms per reqwest client per process.
- Set FABRO_HTTP_PROXY_POLICY=disabled in .cargo/config.toml so all
test HTTP clients skip proxy discovery automatically
- Remove dead #[cfg(test)] branch in http_api.rs; it now relies on the
env var like every other fabro-http consumer
- Remove kind(test) from nextest overrides so timeout budgets apply to
unit tests too, not just integration tests
- Remove unused SessionCookie import in web_auth.rs
Eliminates all 11 flaky nextest timeouts under parallel load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test_secret_store_path() placed secrets directly in /tmp/, meaning all
tests shared /tmp/server.env. Under parallel nextest, tests that needed
SESSION_SECRET would race on this file, and tests that didn't provide one
(auth_login_github_redirects_to_github) would accidentally inherit it
from another test.
Fix: each test now gets its own temp directory via a ULID-keyed subdirectory.
Also fix the github redirect test to explicitly provide its session key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The gh_cli strategy was named after its bootstrap mechanism, not what it
actually is at runtime: a stored token. This rename makes the abstraction
honest and decouples runtime behavior from the gh CLI.
- Rename GithubIntegrationStrategy::GhCli to Token (serialized as "token")
- Rename vault/env secret from GITHUB_CLI_TOKEN to GITHUB_TOKEN
- Accept GH_TOKEN as a fallback in both CLI and server
- CLI no longer shells out to `gh auth token` at runtime; reads from
vault/env like the server already did
- fabro install still bootstraps from `gh auth token` as a one-time op
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make fabro install the only supported GitHub App setup path. This removes
HTTP endpoints and browser routes that mutated local server config, rewrites
/setup as an operator instructions page, and aligns the installer manifest
with the live GitHub OAuth callback and setup URLs.
Clippy's absolute_paths lint requires importing the module rather than
using fully-qualified paths in production code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conflicts resolved:
- install.rs: kept simplified auth (port 32276, no TLS, no username
in merge_server_settings), adapted to origin's input_source API by
removing username from ServerConfigSelection::Write
- serve.rs: kept ProviderCredentials import from origin, dropped
ClientAuth (removed with mTLS)
- server.rs: kept both imports (ServerAuthMethod + Provider)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract generate_session_secret and validate_session_secret to
fabro_util::session_secret, removing duplicate implementations in
install.rs (with private hex module) and start.rs
- Remove dead run_auth_method_for_config/run_auth_method_for_method
from jwt_auth.rs (zero callers)
- Replace test read_dev_token helper with dev_token::read_dev_token_file
which validates the fabro_dev_ prefix rather than just non-empty
- Extract build_unix_socket_probe_client to deduplicate probe client
construction in try_connect/connect_unix_socket_api_client_bundle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the old strategy matrix with server.auth.methods, browser session
cookies, and raw dev-token bearer auth. Remove mTLS auth leftovers, auto-
provision local session secrets, and update tests and docs to the new auth
surface.