Phase 1 of the std::fs lint initiative. Refactors blocking std::fs entry
points that ran inside async contexts. Caller chains either converted to
async (using tokio::fs) or wrapped in tokio::task::spawn_blocking where
sync callers were already natural (Command builders, flock semantics).
HIGH (per-request async hot paths):
- fabro-sandbox local.rs: wrap recursive std::fs::read_dir traversal in
spawn_blocking. Fixes /api/runs/{id}/files stalling workers under
concurrent or deep listings.
- fabro-server static_files.rs: convert serve/serve_install/serve_with_mode
and the static-asset load chain to async; use tokio::fs::read for the
debug-only disk fallback. Cascades through install.rs build_install_router
(now async) and ~17 test call sites.
LOW (async but not per-request):
- fabro-workflow artifact.rs: sync_artifacts_to_env, offload_large_values
→ tokio::fs::read_to_string.
- fabro-workflow artifact_snapshot.rs: compute_artifact_info → async +
tokio::fs::read.
- fabro-server ip_allowlist.rs: load_cache and store_cache → async +
tokio::fs::{read,write,create_dir_all}.
- fabro-server server.rs: wrap worker_command invocation in spawn_blocking
at the async boundary in execute_run_subprocess; keep the sync
worker_command + current_server_target signatures intact.
- fabro-cli server/start.rs: wrap the OpenOptions::open call in
acquire_lock in spawn_blocking; file-lock semantics require a real
std::fs::File, and the flock polling loop stays async with time::sleep.
Deferred:
- fabro-llm load_file_as_base64 (file:// attachment loader): 7 call sites
across 4 providers, each inside sync translators. Left for Phase 3
annotation with a FOLLOW-UP marker; file:// URLs are rare in practice.
Verified: workspace builds, 4131 tests pass, 182 skipped. The lint that
enforces this discipline lands in the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLI's store-run lookup now passes `include_archived=true` so resolve
and bulk operations (archive, unarchive, rm, inspect, rewind) can still find
archived runs. The web UI's hand-maintained `RunStatus` union and display
map learn `archived` with a gray style so archived runs render correctly.
Default `fabro ps` continues to hide archived via `is_active()`; `-a`
shows everything including archived.
Two new top-level commands mirror `fabro rm`'s bulk-by-ID shape: positional
run identifiers, per-ID success/error aggregation, and a final non-zero exit
if any item failed. Calls the new server endpoints from Unit 5. Shared bulk
loop covers both directions and emits structured JSON with an `archived` or
`unarchived` list alongside `errors`. Top-level help snapshot updated.
Adds an `archived → unarchive first` guard to every mutation entry point
that could otherwise hit an opaque 409 or confusing 404 on an archived run:
start, cancel, pause, unpause, submit_answer, and append_run_event server
handlers; the resume operation; and the rewind CLI command. append_run_event
also rejects client-injected `run.archived` and `run.unarchived` bodies so
lifecycle transitions cannot bypass the operations layer. Worker-emitted
run.completed / run.failed events still flow through as before. Fork reads
from the source's metadata branch only — no source mutation — so no guard
is needed there.
Move wait_for_path, wait_for_log_line, stop_pid, server_log_files, and
isolated_storage_dir out of the three integration test files that duplicated
them and into fabro-test's public surface next to apply_test_isolation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Merge prepare_foreground_server_bootstrap and prepare_server_sink_bootstrap
into one prepare_server_bootstrap(config, storage, foreground).
- Drop three one-line settings_layer_* passthroughs from user_config; callers
now use load_settings_with_{storage_dir,config_and_storage_dir} directly.
- Swap underscore-prefixed lock field for #[expect(dead_code, reason=…)] to
document RAII intent explicitly.
- Remove two narrate-what-it-does comments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route server-owned logs to <storage>/logs/server.log from the start of
tracing, remove legacy home/config ownership paths, and fail fast when
a running legacy daemon is detected instead of silently proceeding.
This also adds the missing sink-resolution, truncate/append,
concurrency, legacy-config, and uninstall regression coverage for the
home/storage cleanup plan.
Extends the workspace clippy.toml — which already bans std:🧵:sleep,
std:🧵:spawn, and std::process::Command::new on Tokio paths — with:
- disallowed-types: std::io::{Read, Write, BufRead, BufReader, BufWriter}
and std::net::{TcpStream, TcpListener, UdpSocket}
- disallowed-methods: std::io::{stdin, stdout, stderr}
Non-blocking std::io items (Error, ErrorKind, Result, IsTerminal, Cursor)
remain allowed. std::fs is intentionally deferred.
Annotates ~24 pre-existing sync call sites with #[expect(..., reason = "...")]
matching the established pattern. All annotations describe why blocking I/O
is intentional in that context (sync CLI command, test helper, pre-fork
flush, etc.), so a future conversion to async will surface as an unfulfilled
lint expectation instead of silently drifting.
Fixes one real Tokio-path issue surfaced by the new lint:
fabro-cli's server-start daemon-health poller (try_connect) was a sync fn
called from async execute_daemon; std::net::TcpStream::connect_timeout
blocked a Tokio worker for up to 100ms per poll iteration. Converted to
tokio::net::{TcpStream, UnixStream} with tokio::time::timeout.
One follow-up flagged in-code: fabro-agent/src/cli.rs's JSON event writer
uses std::io::stdout() inside tokio::spawn. Annotated with a FOLLOW-UP
reason pointing at tokio::io::stdout; left unchanged since volume is low
and scope exceeded this pass.
Verified: clippy clean, cargo +nightly fmt --check clean, full nextest
workspace run (4131 passed, 182 skipped).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `RunStatus::Archived` variant and splits the overloaded `is_terminal()`
into `is_terminal()` (reached terminal outcome) and `is_immutable()` (cannot
transition outbound). `can_transition_to()` now allows Succeeded|Failed|Dead to
and from Archived, preserving the `* -> Dead` escape hatch. Downstream
exhaustive matches in the CLI and server are updated with conservative Archived
arms; the server's public-enum mapping and board-column placement carry TODOs
for the OpenAPI update in a later unit.
## Summary
Stacked cleanup of the `canonicalize blocked run status` work (local
commit `d13cdf374`) plus reconciliation with origin's `canonicalize
paginated run list responses` (origin commit `8ab689da7`). Both efforts
ran in parallel and diverged on the column name (`blocked` vs `waiting`)
and on how the board response is shaped — this PR converges them,
keeping `blocked` as the canonical column id while adopting origin's
`column` field on `RunListItem` and `StoreRunSummary` shape.
Also fixes a production-worker regression introduced by the
canonicalization: the worker's start-precondition only accepted
`Submitted | Starting`, so once runs started transitioning through
`Queued` on the way to `Starting`, every subprocess-worker run failed
with `Precondition failed: cannot start run: status is Queued`. That
cascaded into ~90 failing CLI/server integration tests locally.
## Commits
1. `f65843168` refactor(runs): simplify blocked status follow-ups
2. `1492d956c` chore: resolve clippy warnings
3. `676fd9f44` first merge of origin/main
4. `23fc92a2f` **fix(runs): allow Queued status in start precondition**
← the cascade-fix
5. `36b507a83` refactor: simplify pause/unpause + dedupe web status
tables
6. `8d8d27748` refactor(workflow): encapsulate BlockedStateTracker
inside HumanHandler
7. `1c17fda35` second merge of origin/main — resolves waiting vs blocked
8. `4cd3ef7b1` refactor(workflow): Mutex<usize> → AtomicUsize
9. `2e5a58e8a` fix(demo): align run-4 lifecycle status with Blocked
board column
## Test plan
- [x] fmt, clippy, build, doctests all clean
- [x] `cargo nextest run --workspace` — **4092/4092 pass**
- [x] `bun test` — **26/26 pass**, typecheck + production build clean
- [x] Manual CLI repro of the Queued-precondition fix
- [x] Browser smoke test: all 5 columns render with correct
labels/colors, demo run-4 appears in Blocked lane with question text
intact
## Known follow-up (not blocking)
A "paused-while-blocked" run (status `Paused` + `blocked_reason: Some`)
lands in the `running` column because the visible status chooses
`Paused` over `Blocked`. The pending question is not prominent on the
board. Addressing it would require `board_column()` to branch on
`(status, blocked_reason)` rather than just `status` — worth a separate
ticket.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The web-install feature was carrying nine pedantic-tier clippy errors
from its initial commit. Fix them in place:
- \`install.rs\` \`InstallAppState\` switches \`install_token\`,
\`storage_dir\`, and \`config_path\` from \`Arc<String>/Arc<PathBuf>\` to
\`Arc<str>/Arc<Path>\` so we stop heap-duplicating buffers.
- Bring \`Infallible\`, \`axum::middleware\`, \`axum::extract::Request\`,
and \`fabro_types::settings::SettingsLayer\` into scope instead of
using absolute paths inline.
- Replace \`Duration::from_secs(10 * 60)\` with \`Duration::from_mins(10)\`.
- \`generate_ephemeral_secret\` never returns \`Err\`; drop the \`Result\`.
- \`server/start.rs ensure_storage_server_autostart_allowed\` takes
\`Option<&OsStr>\` instead of consuming an \`OsString\` it only reads.
- \`server/mod.rs\` storage_dir fallback uses \`map_or_else\` to satisfy
\`map_unwrap_or\`.
CI now passes \`cargo +nightly-2026-04-14 clippy --workspace
--all-targets -- -D warnings\` cleanly and the 892-test suite still
passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `fabro-install` crate was introduced for the web wizard but the CLI
kept its own copies of the same JWT keypair generation, TOML merging,
and GitHub auth settings helpers. Delete the duplicates and route the
CLI through `fabro_install::*`. The CLI keeps a thin
`merge_server_settings` wrapper because it only ever binds TCP and
derives the authority from `--web-url`.
Also tighten `persist_install_outputs_direct` to take its
`PendingSettingsWrite` argument by reference (satisfies
`needless_pass_by_value`) and pull the remaining absolute paths in the
crate's test module into `use` statements, clearing the nightly clippy
warnings that this branch was carrying.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Harden the remaining install flow regressions and add the missing
coverage for startup dispatch, finish-time shutdown behavior, and
partial-state persistence after vault failures.
Tighten the browser-based install flow after correctness and adversarial
review, without changing the external wizard shape.
- Persist the actual bind in server.listen, not the canonical URL
- Reject concurrent /install/finish and rapid GitHub App retries
- Keep the prior GitHub Token strategy until App callback succeeds
- Recover from poisoned install locks instead of propagating panics
- Rollback both settings and vault on failed persistence
- Redirect GitHub callback errors back into the wizard UI
- Validate LLM keys via /models probe instead of a billed generate()
- Reject canonical URLs with trailing slash, path, query, or fragment
- Accept any valid install-token source, not just the first present one
- Redact the install token in structured logs
- Assert install-mode SPA marker injection at startup
- Warn on suspected concurrent operators via UA + X-Forwarded-For
- Add component-level test for the GitHub callback error banner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement the web-first install experience across the server, CLI, API spec,
web app, and packaged SPA assets.
This also removes test-side process env mutation by pushing env-dependent
decision points behind explicit helpers and test wiring.
Unify /api/v1/runs and /api/v1/boards/runs around a shared
paginated summary contract with additive convenience fields.
Update the server, demo data, generated clients, CLI pagination,
and web consumers so board views become a thin projection over the
canonical run summary surface.
Integration tests spawn the fabro binary as a subprocess and call
env_clear() for isolation, which strips LLVM_PROFILE_FILE. Under
cargo-llvm-cov this dropped subprocess coverage into orphaned
default.profraw files in tempdirs instead of the merged profile.
Add a preserve_coverage_env! macro in fabro-test and call it after
each env_clear() in apply_test_isolation, LightweightCli, and the
exec.rs sites. No-op when the env var is unset (normal test runs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-release builds now append the profile to `fabro --version`
(`x.y (sha date debug)`), `fabro version`, and `fabro system info`,
so users can tell a local build apart from a shipped release. The
API's `SystemInfoResponse` gains a `profile` field so the client
can render the server's build profile too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When stderr is a TTY and text output is used, print a yellow `warning:`
line on stderr if the server reports a version that differs from the
client. JSON output and non-interactive contexts stay silent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLI integration tests spawned the real fabro binary while letting the
parent process's env pass through. The pr_view "no credentials" snapshot
failed in CI because the Nightly workflow's minted GITHUB_TOKEN was
inherited by the child and turned the expected "credentials required"
error into a real GitHub API call (404 / 401). On developer laptops the
same leak occurs whenever gh auth login is active.
Introduce apply_test_isolation(cmd, home) in fabro-test: env_clear() +
re-populate PATH, HOME, NO_COLOR, and the FABRO_* test overrides. Route
TestContext::command(), the internal server bootstrap, and the four
ad-hoc spawners in tests/it/cmd/{attach,render_graph,runner,server_start}
through the same helper so the isolation is systemic instead of
per-callsite. Tests that deliberately need a credential (OPENAI_API_KEY,
GITHUB_APP_PRIVATE_KEY, etc.) continue to set it explicitly on the
returned Command; those survive the clear.
Add a regression test that sets sentinel GITHUB_TOKEN and
ANTHROPIC_API_KEY in the parent, spawns /usr/bin/env through the helper,
and asserts the child sees neither credential while still seeing PATH
and the harness's FABRO_NO_UPGRADE_CHECK override.
Verified: the full workspace (4022 tests) passes with GITHUB_TOKEN and
ANTHROPIC_API_KEY set in the parent, which previously broke the
pr_view_reads_pull_request_from_store_without_pull_request_json
snapshot. cargo fmt and nightly clippy are clean.
daytona-sdk transitively pulls native-tls via reqwest (its own
reqwest v0.12, separate from our rustls-configured workspace
reqwest v0.13). native-tls requires libssl headers at build time,
which musl-gcc cannot satisfy from the host's glibc libssl-dev.
Add a target-specific openssl dep with the vendored feature so
openssl-sys compiles openssl from source for musl builds. glibc
builds are unaffected — they continue to link against the system
libssl that CI runners already have.
Verified end-to-end: aarch64-unknown-linux-musl binary built locally
runs on Alpine 3.20 (pure musl userspace).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the release matrix to two statically-linked musl variants so
Alpine and other musl-based Linux hosts can install without glibc.
Homebrew and the Docker image remain glibc-only.
- release.yml: add x86_64-unknown-linux-musl (ubuntu-24.04) and
aarch64-unknown-linux-musl (ubuntu-24.04-arm) matrix rows with
musl-tools, CC_*_musl, CARGO_TARGET_*_LINKER, and LIBZ_SYS_STATIC
- Cargo.toml: enable git2 vendored-libgit2 so libgit2 compiles from
source for every target (needed because musl cannot link against
Ubuntu's glibc-built libgit2-dev)
- install.sh: check `ldd --version` for "musl" and rewrite the target
from -gnu to -musl so Alpine users get the right tarball
- upgrade.rs: add detect_linux_libc() / parse_ldd_libc() helper and
route detect_target() Linux arms through it, with unit tests
covering glibc, musl, empty, and unknown output
- tests/it: extend target regex in the dry-run snapshot filter
Ubuntu 24.04 is required for the musl runner: 22.04 ships musl 1.2.2
which SIGSEGVs statically-linked x86_64 test binaries at startup.
Confirmed against graphviz-sys CI before landing here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clippy flagged hard_link_or_copy's match as single_match_else;
rewrite as an early-return if. rustfmt reformatted the long
chained path join in brew_command and the multi-arg
hard_link_or_copy call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add process-level upgrade tests that invoke the real fabro binary from a
fake Homebrew Cellar path so current_exe() detection is exercised end to
end. Update the CLI reference and changelog to document the Homebrew-managed
upgrade path and the flags that remain self-managed-only.
Detect Homebrew-managed installs from the canonicalized executable path
(Cellar/fabro[-nightly]/...) and branch both the background nag and
`fabro upgrade` accordingly.
- Background check fetches the tap's versions.json (raw.githubusercontent)
for the matching channel instead of GitHub's latest release, so the nag
tracks what `brew update` can satisfy.
- Cache payload gains an install_source tag; entries from a different
source (or legacy entries without the field) are ignored, so users who
switch between fabro/fabro-nightly/tarball don't see stale versions.
- `fabro upgrade` on a brew install refuses to overwrite the
Homebrew-managed binary and prints `brew upgrade fabro[-nightly]`.
--dry-run prints the command and exits 0; --version/--prerelease/--force
are rejected with a clear Homebrew-managed error.
- Manifest/exe-resolve failures on brew installs skip the notice rather
than falling back to GitHub latest (which would reintroduce the
tap-lag false positives this change is meant to remove).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A recent CI flake surfaced as bare "error: No such file or directory
(os error 2)" with no chain, because the failing operation lived behind
a raw `?` on a `std::fs::` / `File::create` / `Command::spawn` call. The
error had no verb, no path, no hint at which step in server startup
broke. Retry loops were explicitly rejected -- the goal is to diagnose
the next occurrence, not mask it.
Wraps 50+ such sites across fabro-cli, fabro-server, fabro-workflow,
fabro-util, fabro-vault, fabro-telemetry, fabro-interview, fabro-llm,
and fabro-devcontainer with `.with_context(|| format!("<verb> {path}"))`
so anyhow's error chain carries both the operation and the path when
an io error escapes.
Where the enclosing function returns `io::Result` (fabro-util run_log,
fabro-interview recording, fabro-llm attachment loader), the error is
re-wrapped via `io::Error::new` to keep the signature stable. Where a
crate uses its own thiserror enum, either a new `io_context` helper
was added (fabro-vault) or the path was folded into the existing
`Error::Io(String)` message (fabro-workflow).
No retry loops. No behavior changes. Skipped sites documented:
`.ok()`-swallowed, `match ErrorKind::NotFound`, `let _ = ...`, typed
error variants that already carry the path, and test modules.
Verified: cargo build --workspace, cargo +nightly clippy --workspace
--all-targets -- -D warnings, cargo nextest run --workspace (3991/3991
pass).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the docs website shortcut alongside the other onboarding
commands so new users can find the web docs without hunting through
`fabro help`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace the three-line "help along the way" trio with a Discord
callout in the style of qlty's landing: one line pointing at per-
command --help, one inviting users to the Fabro Discord.
- New final line uses qlty's dim/cyan split: "For a full list of
commands, run `fabro help`."
- Drop `sandbox cp` and `sandbox preview` from the curated list;
`sandbox ssh` stays. Both are still reachable via `fabro help`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves `server start` and `secret set` into Set up so the first section
covers everything a user does once before they have a working install.
Drops `secret list` from the landing — `fabro --help` still surfaces it.
Merges the run-inspection commands (`logs`, `sandbox ssh`, `sandbox
preview`, `sandbox cp`) under a single "Inspect runs" heading, since
they all answer "what happened in this run?".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Running `fabro` with no subcommand now prints a short, colorized guide
highlighting the most important commands (install, doctor, repo init,
validate, preflight, run, logs, server start, secret set/list, sandbox
ssh/preview/cp) instead of clap's full --help dump.
`fabro --help` and `fabro help` still render clap's comprehensive
reference unchanged. The CLI makes the root subcommand optional and
intercepts the None case in main_inner before telemetry or logging
init; the empty command name also suppresses the "CLI Executed"
tracking event for this pseudo-command.
Includes an inline-snapshot IT test covering the full landing body and
updates four pre-existing usage-line snapshots (<COMMAND> → [COMMAND])
that reflect the now-optional subcommand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In release builds with SEGMENT_WRITE_KEY baked in (i.e. CI), the
post-command telemetry flush calls spawn_fabro_subcommand, which in turn
does create_dir_all(~/.fabro/tmp) and writes a JSONL event file. That
silently undoes the directory removal that `fabro uninstall --yes` just
performed — leaving a stray ~/.fabro/tmp/ behind and breaking the
uninstall integration tests on CI release runs.
Fixes:
- run_uninstall calls fabro_telemetry::shutdown() before removal so the
buffered "CLI Executed" track in main() can't be queued or flushed,
and the background thread can't spawn the sender subprocess.
- TestContext::command() exports FABRO_TELEMETRY=off so test subprocesses
never initialise telemetry at all, as a belt-and-suspenders guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- server_start::help: correct --watch-web description indent from 14 to 10 spaces
in the stripping regex so the filter matches in debug builds where the flag
is present in --help output.
- fabro-telemetry: split telemetry_level default test into debug/release
variants. The function's default depends on cfg!(debug_assertions), so the
prior single test panicked under cargo test --release.
Verified: cargo nextest run --workspace (debug) → 3990 passed; cargo test
--workspace --release → all 69 test binaries pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Update VERSION filter regex to handle prerelease suffixes (e.g., 0.204.0-beta.1)
- Add VERSION filter to JSON snapshots in fabro_json_snapshot macro
- Fix attach test to use [VERSION] placeholder instead of hardcoded version
- Update upgrade help snapshot to include new --prerelease flag
- Change fake version in upgrade test from v0.176.3 to v999.0.0 to avoid collision
- Strip --watch-web from server start help (debug-only flag, varies by build)
- Gate test_panic module with #[cfg(debug_assertions)] (debug-only command)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Widens the candidate set to include prereleases, picks max semver across
stable + prereleases. Falls back to /releases/latest if no parseable
non-draft tag is returned. Conflicts with --version. Background
auto-upgrade notice remains stable-only.
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>
Stops any running server then starts a fresh one, passing through all
the same flags as `server start` (--watch-web, --foreground, etc.).
Works even if no server is currently running.
Co-Authored-By: Claude Opus 4.6 (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>
--web-url defaulted to http://localhost:3000 but merge_server_settings
hardcoded http://127.0.0.1:32276, causing GitHub OAuth redirect_uri
mismatch. Now both derive from the same --web-url flag (default:
http://127.0.0.1:32276 via DEFAULT_TCP_PORT).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Print the HTTP URL (cyan) and enabled auth methods after server start,
so users can see at a glance how to access the server and what login
methods are available.
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>
- Add [GRAPH_PATH] filter to run_output_filters so dry_run_simple
snapshot is path-independent
- Double fabro-cli slow-timeout (3s → 6s) to prevent ps test timeouts
- Preserve cloud sandboxes; bump snapshot to fabro-v7 with 8 CPU / 16GB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The snapshot was overfit to local env — it hardcoded Anthropic models
passing because the server inherited ANTHROPIC_API_KEY from the test
runner. On CI with no API keys, all models are skipped and the snapshot
diverged.
Replace the snapshot with targeted assertions: exit code 0 and "Skipped"
appears in stderr. This works regardless of which credentials are
available in the test environment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>