diff --git a/apps/fabro-web/app/routes/run-settings.tsx b/apps/fabro-web/app/routes/run-settings.tsx
index 49582e6db..b29928e04 100644
--- a/apps/fabro-web/app/routes/run-settings.tsx
+++ b/apps/fabro-web/app/routes/run-settings.tsx
@@ -95,7 +95,7 @@ export default function RunSettingsPage({ loaderData }: any) {
{dotReady && (
diff --git a/docs-internal/events-strategy.md b/docs-internal/events-strategy.md
index 33b945a98..2ec664223 100644
--- a/docs-internal/events-strategy.md
+++ b/docs-internal/events-strategy.md
@@ -1,6 +1,6 @@
# Fabro Events Strategy
-Fabro emits structured **workflow run events** during execution for observability. Events are the durable audit trail for a run: they drive `progress.jsonl`, `live.json`, the run store, SSE streaming, CLI progress rendering, and retro analysis.
+Fabro emits structured **workflow run events** during execution for observability. Events are the durable audit trail for a run: they drive the run store, SSE streaming, CLI progress rendering, retro analysis, and optional JSONL sinks.
Events are distinct from tracing logs. Tracing is developer diagnostics; events are product-facing state transitions and activity records that other systems consume.
@@ -10,12 +10,12 @@ Detached runs rely on this distinction. If something needs to be visible after r
```text
Engine/Handler -> Event -> Emitter::emit()
- |- trace(raw event)
- |- canonicalize -> RunEvent
- `- on_event(&RunEvent)
- |- progress.jsonl + live.json
+ |- trace(raw event)
+ |- canonicalize -> RunEvent
+ `- on_event(&RunEvent)
|- run store
|- SSE
+ |- optional JSONL/debug sinks
`- CLI / tests / metrics listeners
```
@@ -29,7 +29,7 @@ The canonical `RunEvent` is built exactly once in `fabro-workflow/src/event.rs`.
## Canonical Envelope
-Each line in `progress.jsonl` is a serialized `RunEvent`:
+Each serialized `RunEvent` uses this canonical envelope:
```json
{
@@ -171,6 +171,6 @@ Do not rebuild or mutate the `RunEvent` in downstream listeners.
## Bypass And Persistence Guarantees
-`progress.jsonl`, the run store, and SSE should reflect the same canonical envelope bytes after redaction.
+Any JSONL sink, the run store, and SSE should reflect the same canonical envelope bytes after redaction.
`status.json` remains the authoritative completion signal for detached runs. Terminal run status should only be written after all post-run work is finished.
diff --git a/docs-internal/events.md b/docs-internal/events.md
index bf29a9ef7..71174e0d7 100644
--- a/docs-internal/events.md
+++ b/docs-internal/events.md
@@ -1,6 +1,6 @@
# Events
-Every event in `progress.jsonl` is a JSON object with this envelope structure:
+Every serialized run event envelope, whether streamed over SSE, returned by `fabro logs`, or written to a JSONL sink, uses this structure:
```json
{
diff --git a/docs-internal/logging-strategy.md b/docs-internal/logging-strategy.md
index f2c95a050..08d6a8a09 100644
--- a/docs-internal/logging-strategy.md
+++ b/docs-internal/logging-strategy.md
@@ -23,7 +23,7 @@ Production runs at INFO level. INFO should be low-volume and high-signal — the
- Hot loops or per-token streaming events (use DEBUG only if truly needed for diagnosis)
- Data that belongs in user-facing output (`eprintln!` for interactive CLI feedback, not tracing)
-- Detached user-visible warnings or errors that need to survive `attach`/`logs` (`detach.log` is debug-only; emit a `Event` into `progress.jsonl` instead)
+- Detached user-visible warnings or errors that need to survive `attach`/`logs` (`detach.log` is debug-only; emit an `Event` into the run event stream instead)
- Redundant information already captured by a parent event (if you logged "starting X", you don't need to log every sub-step at the same level)
- Events that are already traced via `EventEnum::trace()` — the event enums (`AgentEvent`, `PipelineEvent`, `ExecutionEnvEvent`) each have a `trace()` method called automatically at their emit site; do not add manual `info!`/`debug!` calls that duplicate what `trace()` already emits
- Wrapper/forwarding variants that re-emit an inner event — `PipelineEvent::Agent`, `PipelineEvent::ExecutionEnv`, and `AgentEvent::SubAgentEvent` are no-ops in `trace()` because the inner event is already traced at its origin
diff --git a/docs-internal/run-directory-keys.md b/docs-internal/run-directory-keys.md
index 89568554f..a77029c3f 100644
--- a/docs-internal/run-directory-keys.md
+++ b/docs-internal/run-directory-keys.md
@@ -1,6 +1,6 @@
# Run Scratch Files
-This document maps the files written under a run scratch directory to their event sources.
+This document maps the files that still live under a run scratch directory. Durable run state lives in the run store and metadata branch; scratch is mostly local runtime state and caches.
Scope:
- Scratch root: `~/.fabro/scratch/YYYYMMDD-{run_id}/`
@@ -12,19 +12,10 @@ There is no `_init.json` anymore. Run existence in the database is determined by
## Root-Level Files
-| File | Purpose | Event source |
+| File | Purpose | Source |
|---|---|---|
-| `run.json` | Run metadata snapshot: run id, resolved settings, graph, workflow slug, working directory, repo info, labels | Primarily `run.created`, plus request-time inputs persisted during create |
-| `start.json` | Start timestamp and git context | `run.started` |
-| `workflow.fabro` | Original dot source when available | `run.created.properties.workflow_source` |
-| `workflow.toml` | Original workflow TOML when available | `run.created.properties.workflow_config` |
-| `progress.jsonl` | Append-only event log | Every emitted run event |
-| `live.json` | Latest live state snapshot | Derived continuously from emitted events |
-| `checkpoint.json` | Crash-recovery snapshot | Derived from accumulated stage/checkpoint events plus engine state |
-| `conclusion.json` | Final outcome summary | `run.completed` and `run.failed` |
-| `retro.json` | Post-run retro output | `retro.completed` |
+| `workflow_bundle.json` | Bundled workflow input used by `start` to restore `workflow_path` and bundled child workflows/files | Written during create from the resolved workflow bundle |
| `final.patch` | Final git diff for checkpointed runs | Local git state at finalize time, not a direct event payload |
-| `cli.log` | Per-run tracing log | Local tracing output, not event-derived |
| `run.pid` | Legacy detached-run pid file from older runs | Legacy only; current flows do not rely on it |
## Local-Only Directories
@@ -35,45 +26,18 @@ These paths are local runtime state, not canonical event projections.
|---|---|
| `worktree/` | Git worktree used by checkpointed runs |
| `runtime/blobs/` | Materialized local blob payloads for file-backed `fabro+blob://` references |
-| `cache/artifacts/values/` | Large context values spilled to the filesystem |
+| `runtime/worker.stderr.log` | Server-managed worker stderr capture |
| `cache/artifacts/files/` | Captured artifact files organized by node and retry |
+| `nodes/{manager_node}_{visit}/child/` | Nested scratch root for manager-loop child workflows |
-## Node Directories
+## Reconstructed / Exported Files
-Per-node outputs are written under:
-- `nodes/{node_id}/`
-- `nodes/{node_id}-visit_{N}/` for retries, where `N` starts at `2`
+These names are still real, but they are no longer live scratch files by default:
-### Agent and prompt nodes
-
-| File | Purpose | Event source |
-|---|---|---|
-| `prompt.md` | Rendered prompt text | `stage.prompt.properties.text` is the closest event source |
-| `response.md` | Final model response text | Reconstructable from message events, but written as a local convenience file |
-| `status.json` | Final node status, notes, failure reason, timestamp | `stage.completed` |
-
-### Command nodes
-
-| File | Purpose | Event source |
-|---|---|---|
-| `script_invocation.json` | Command metadata: command, language, timeout | Partly from node config; not fully represented by a single event |
-| `stdout.log` | Captured stdout | Local process output |
-| `stderr.log` | Captured stderr | Local process output |
-| `script_timing.json` | Duration, exit code, timeout result | Partly `stage.completed.properties.duration_ms`, otherwise local process state |
-| `status.json` | Final node status | `stage.completed` |
-
-### Nodes with git checkpointing
-
-| File | Purpose |
-|---|---|
-| `diff.patch` | Per-node git diff captured at checkpoint time |
-
-### Manager / child workflow nodes
-
-Manager nodes may create a nested `child/` directory containing a full run scratch structure for the child workflow.
+- Metadata branch files such as `run.json`, `start.json`, `checkpoint.json`, and `retro.json`
+- `fabro store dump` exports such as `run.json`, `start.json`, `status.json`, `checkpoint.json`, `conclusion.json`, `retro.json`, `events.jsonl`, and per-node prompt/response/status/stdout/stderr files
+- Retro-agent temp uploads named `progress.jsonl`, `checkpoint.json`, `run.json`, and `start.json` inside the retro sandbox
## Notes
-- `progress.jsonl` is the event log; many other files are denormalized convenience snapshots derived from that stream plus local runtime state.
-- `checkpoint.json` and `live.json` are projections, not single-event payloads.
- Artifact binaries are no longer stored in the SlateDB keyspace. They live in `ArtifactStore`; the run scratch tree only contains local cached copies when a workflow stage writes them to disk.
diff --git a/docs/agents/outputs.mdx b/docs/agents/outputs.mdx
index 60628c6a3..ef1927b5c 100644
--- a/docs/agents/outputs.mdx
+++ b/docs/agents/outputs.mdx
@@ -119,7 +119,7 @@ The tracked paths are stored as `files_touched` on the stage outcome:
| Location | How it's used |
|---|---|
-| `StageCompleted` event | Emitted with `files_touched` in the event stream and `progress.jsonl` |
+| `StageCompleted` event | Emitted with `files_touched` in the event stream and surfaced by `fabro logs` / exported event streams |
| Preambles | Listed under each completed stage so downstream agents know what changed |
| Retros | Included per-stage and aggregated across the full run |
| `status.json` | Written to the stage's logs directory after each node completes |
diff --git a/docs/execution/checkpoints.mdx b/docs/execution/checkpoints.mdx
index 82e985ddd..4c8ae13aa 100644
--- a/docs/execution/checkpoints.mdx
+++ b/docs/execution/checkpoints.mdx
@@ -3,7 +3,7 @@ title: "Checkpoints"
description: "How Fabro uses Git to checkpoint and resume workflow runs"
---
-Fabro checkpoints every workflow run using Git. After each node completes, Fabro commits the file changes and execution state so that interrupted runs can be resumed exactly where they left off. This happens automatically — no configuration required beyond running inside a Git repository.
+Fabro checkpoints every workflow run using Git plus the durable run store. After each node completes, Fabro commits the file changes and execution state so that interrupted runs can be resumed exactly where they left off. This happens automatically — no configuration required beyond running inside a Git repository.
## Two branches, two purposes
@@ -69,7 +69,7 @@ The `checkpoint.json` captures everything needed to resume a run:
| `loop_failure_signatures` | Failure signature counts for loop detection |
| `restart_failure_signatures` | Failure signature counts across loop-restart edges |
-The checkpoint is also saved to `checkpoint.json` in the run directory for quick local access.
+The durable run store also keeps the current checkpoint so `resume`, `inspect`, and API reads do not need to rely on scratch files.
## Worktrees
@@ -90,20 +90,20 @@ For Daytona sandboxes, the worktree is created inside the remote sandbox instead
## Resuming a run
-Resume an interrupted run from its checkpoint on disk:
+Resume an interrupted run from its durable checkpoint:
```bash
fabro resume 01JKXYZ
```
-Fabro looks up the run directory by ID prefix, loads `checkpoint.json` and `run.json` from the run directory, and spawns a new engine process to continue execution. No workflow file or override flags are needed — all configuration is read from the persisted run state.
+Fabro resolves the run by ID prefix, validates that durable state contains a checkpoint, and asks the server to continue execution. No workflow file or override flags are needed — all configuration is restored from persisted state.
1. Fabro looks up the run directory by ID prefix
-2. Validates that `checkpoint.json` exists and no engine process is already running
-3. Cleans stale artifacts from the previous execution (conclusion, PID file, etc.)
+2. Validates that a checkpoint exists in durable state and no engine process is already running
+3. Cleans stale local artifacts from the previous execution
4. Resets status to `Submitted` and spawns a new engine subprocess with `--resume`
-5. The engine loads `run.json` and `checkpoint.json`, restores the full context, completed node list, retry counts, and failure signatures
+5. The engine restores the full context, completed node list, retry counts, and failure signatures from durable state
6. If the checkpointed node used `full` fidelity, downgrades the first resumed node to `summary:high` (since the original conversation thread no longer exists in memory)
7. Continues execution from `next_node_id`
@@ -112,12 +112,12 @@ Fabro looks up the run directory by ID prefix, loads `checkpoint.json` and `run.
Here's the full sequence that runs after every node completes:
-1. **Save checkpoint to disk** — Write `checkpoint.json` to the run directory
+1. **Append checkpoint event** — Persist the new checkpoint into durable run state
2. **Write metadata branch** — Serialize the checkpoint and any new artifacts to the metadata branch (shadow commit)
3. **Commit to run branch** — Stage all file changes, commit with structured trailers linking to the shadow commit SHA
-4. **Update checkpoint** — Re-save `checkpoint.json` with the `git_commit_sha` field set
+4. **Update durable checkpoint** — Persist the `git_commit_sha` associated with the run-branch commit
-Steps 2-4 are best-effort — if any Git operation fails, the run continues and emits a `RunNotice` warning event. The disk checkpoint from step 1 is always available as a fallback.
+Steps 2-4 are best-effort — if any Git operation fails, the run continues and emits a `RunNotice` warning event. Resume still uses the durable checkpoint in the run store.
## Inspecting run history
diff --git a/docs/execution/observability.mdx b/docs/execution/observability.mdx
index 309fc2bf1..80eff8ed8 100644
--- a/docs/execution/observability.mdx
+++ b/docs/execution/observability.mdx
@@ -9,10 +9,11 @@ Fabro captures a structured event for every significant action during a workflow
Every workflow run emits a sequence of canonical **run event envelopes** that are:
-- Written to `progress.jsonl` in the run directory
+- Stored durably in the run store
- Broadcast over SSE to connected API clients
- Stored for later analysis and retro generation
- Rendered by CLI progress and log tooling
+- Optionally materialized into JSONL by export/debug paths
### Event names
@@ -28,7 +29,7 @@ Event names use lowercase dot notation, for example:
### Envelope format
-Each line in `progress.jsonl` is a JSON object with a stable envelope:
+Each serialized event envelope has a stable JSON shape:
```json
{
@@ -64,25 +65,23 @@ Envelope fields:
Only `id`, `ts`, `run_id`, and `event` are always present. Optional fields are omitted when they do not apply.
-## Reading `progress.jsonl`
+## Reading the event stream
Because event payload lives in `properties`, most shell queries should look there.
```bash
# Count tool calls in a run
-jq -r 'select(.event == "agent.tool.started") | .properties.tool_name' \
- ~/.fabro/scratch/01JKXYZ.../progress.jsonl | wc -l
+fabro logs 01JKXYZ... | jq -r 'select(.event == "agent.tool.started") | .properties.tool_name' | wc -l
# Find stage failures
-jq 'select(.event == "stage.failed")' \
- ~/.fabro/scratch/01JKXYZ.../progress.jsonl
+fabro logs 01JKXYZ... | jq 'select(.event == "stage.failed")'
# See which edges were taken
jq '{from: .properties.from_node, to: .properties.to_node, label: .properties.label}' \
- ~/.fabro/scratch/01JKXYZ.../progress.jsonl | head
+ <(fabro logs 01JKXYZ...) | head
```
-`live.json` is still a pretty-printed copy of the most recent event envelope.
+If you need files on disk for offline analysis, `fabro store dump` exports `events.jsonl` plus run-state projections.
## Event categories
@@ -111,7 +110,7 @@ Lifecycle events such as `agent.sub.spawned` and `agent.sub.completed` are emitt
### API: Server-Sent Events
-When running workflows through the API server, subscribe to the [run events endpoint](/api-reference/runs/stream-run-events). Each SSE payload is a serialized run event envelope in the same shape used by `progress.jsonl`.
+When running workflows through the API server, subscribe to the [run events endpoint](/api-reference/runs/stream-run-events). Each SSE payload is a serialized run event envelope in the same shape used by `fabro logs` and `events.jsonl` exports.
### Web UI
@@ -127,16 +126,12 @@ The CLI renders live progress from the same envelope format. This is written to
## Post-run analysis
-Run artifacts still include:
+Post-run analysis surfaces include:
-| File | Description |
+| Surface | Description |
|---|---|
-| `run.json` | Run metadata and graph |
-| `start.json` | Start record |
-| `progress.jsonl` | Full event envelope stream |
-| `live.json` | Latest event envelope |
-| `checkpoint.json` | Final execution state |
-| `retro.json` | Retrospective, when enabled |
-| `conclusion.json` | Terminal summary |
+| `fabro logs
` | Full event envelope stream as NDJSON |
+| `fabro inspect ` | Current durable run state, including run/start/checkpoint/conclusion records |
+| `fabro store dump --output ` | Exported `events.jsonl` plus reconstructed JSON and node files |
See [retros](/execution/retros), [stages](/api-reference/run-internals/list-run-stages), and [turns](/api-reference/run-internals/list-stage-turns) for higher-level analysis views built on top of this event stream.
diff --git a/docs/execution/retros.mdx b/docs/execution/retros.mdx
index e62d0f043..44471a74a 100644
--- a/docs/execution/retros.mdx
+++ b/docs/execution/retros.mdx
@@ -30,7 +30,7 @@ The quantitative layer is extracted directly from the [checkpoint](/execution/ch
### Narrative layer
-An LLM agent reads the run's `progress.jsonl` event stream and produces a structured analysis:
+An LLM agent reads the run's full event stream and produces a structured analysis:
| Field | Description |
|---|---|
@@ -99,9 +99,9 @@ Open items capture follow-up work identified during the run:
Retro generation happens in two phases after a run completes:
-1. **Derive** — Fabro extracts stage durations from `progress.jsonl` and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer. The retro is saved immediately as `retro.json` in the run's directory.
+1. **Derive** — Fabro extracts stage durations from durable run events and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer.
-2. **Narrate** — An LLM agent session analyzes the run data. The agent has read access to `progress.jsonl`, `checkpoint.json`, `run.json`, and `start.json`. It uses grep and read tools to find interesting signals — failures, retries, errors, approach changes — then calls a `submit_retro` tool with its structured analysis. The narrative fields are merged into the existing retro and saved.
+2. **Narrate** — An LLM agent session analyzes the run data. The agent receives temp files named `progress.jsonl`, `checkpoint.json`, `run.json`, and `start.json` inside its sandbox so it can grep and read the event stream and run state. The narrative fields are merged back into durable retro state.
Both phases run automatically at the end of every CLI run. The API server derives the quantitative layer but does not currently run the narrative agent.
@@ -113,13 +113,6 @@ Both phases run automatically at the end of every CLI run. The API server derive
### CLI
-Retros are saved to `{run_dir}/retro.json` after every run. The path is printed at the end of the run output:
-
-```
-Retro: smooth — Successfully implemented the feature
- Retro saved to ~/fabro-logs/01JKXYZ.../retro.json
-```
-
To enable retros for your project, set `retros = true` in the `[features]` section of your `fabro.toml`:
```toml title="fabro.toml"
@@ -148,4 +141,4 @@ Retros are also available via the REST API. See the [list retros](/api-reference
## Storage
-Retros are stored as `retro.json` in the run's directory alongside `checkpoint.json` and `progress.jsonl`. They are plain JSON files — easy to parse, query, or pipe into other tools.
+Retros are stored in durable run state. If you need files on disk, `fabro store dump` materializes the retro as `retro.json` alongside other exported run data.
diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx
index 1516b359b..3a1d02c2c 100644
--- a/docs/reference/cli.mdx
+++ b/docs/reference/cli.mdx
@@ -198,7 +198,7 @@ The table shows run ID, status, workflow name, goal, and timing.
| `--before ` | Only show runs started before this date (YYYY-MM-DD prefix match) |
| `--workflow ` | Filter by workflow name (substring match) |
| `--label ` | Filter by label (repeatable, AND semantics) |
-| `--orphans` | Include orphan directories (no `run.json`) |
+| `--orphans` | Include orphan directories (no matching durable run) |
| `--json` | Output as JSON |
| `-q, --quiet` | Only display full run IDs, one per line (no headers or footers). Takes precedence over `--json`. |
@@ -255,7 +255,7 @@ fabro system prune --orphans --yes
| `--older-than ` | Only prune runs older than this duration (e.g. `24h`, `7d`). Default when no explicit filters are set: `24h` |
| `--workflow ` | Filter by workflow name (substring match) |
| `--label ` | Filter by label (repeatable, AND semantics) |
-| `--orphans` | Include orphan directories (no `run.json`) |
+| `--orphans` | Include orphan directories (no matching durable run) |
| `--yes` | Actually delete (default is dry-run) |
---
diff --git a/docs/reference/run-directory.mdx b/docs/reference/run-directory.mdx
index 91da2b393..af9f4ca8f 100644
--- a/docs/reference/run-directory.mdx
+++ b/docs/reference/run-directory.mdx
@@ -4,13 +4,13 @@ description: "Structure of Fabro's per-run directory"
---
- The run directory structure and file formats described here are internal implementation details and subject to change without notice. Do not build tooling that relies on them. The authoritative source of run state is the event-sourced run store — files in the scratch directory are disk projections for debugging convenience.
+ The run directory structure and file formats described here are internal implementation details and subject to change without notice. Do not build tooling that relies on them. The authoritative source of run state is the event-sourced run store; the scratch directory is now mostly local runtime state and caches.
-Each `fabro run` invocation creates a timestamped directory under `~/.fabro/scratch/`:
+Each `fabro run` invocation creates a timestamped directory under `~/.fabro/storage/scratch/`:
```
-~/.fabro/scratch/20260307-01JQXYZ123ABC456DEF789/
+~/.fabro/storage/scratch/20260307-01JQXYZ123ABC456DEF789/
```
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the base storage directory with the global `--storage-dir` flag (the scratch directory will be `/scratch/`).
@@ -19,62 +19,28 @@ The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to
| File | Format | When written | Description |
|---|---|---|---|
-| `run.json` | JSON | Run create | Run metadata — `run_id`, `created_at`, `config` (resolved configuration), `graph` (Graph), `workflow_slug`, `working_directory`, `host_repo_path`, `base_branch`, `labels` |
-| `start.json` | JSON | Run start | Start metadata — `run_id`, `start_time`, `run_branch`, `base_sha` |
-| `workflow.fabro` | Graphviz | Run create | Copy of the original workflow graph when the raw DOT source is available |
-| `run.pid` | Text | Legacy only | Legacy process ID file from older runs. Current detached launches use launcher records instead, and current attach/resume no longer read `run.pid`. |
-| `workflow.toml` | TOML | Run create | Copy of the original workflow file (only when the workflow is defined in TOML) |
-| `progress.jsonl` | JSONL | Continuous | Event stream — one JSON object per line for every significant event (stage starts, completions, tool calls, retries, etc.). See [Observability](/execution/observability) for the full event catalog. |
-| `live.json` | JSON | Continuous | Current execution state snapshot, overwritten on each event. Used for live monitoring. |
-| `checkpoint.json` | JSON | After each node | Crash recovery state — `current_node`, `completed_nodes`, `node_retries`, `context_values`, `node_outcomes`, `next_node_id`, `git_commit_sha`, failure signatures. See [Checkpoints](/execution/checkpoints). |
-| `conclusion.json` | JSON | Run end | Final result — `status`, `duration_ms`, `failure_reason`, `final_git_commit_sha`. Only present when the run completes (not for crashed or interrupted runs). |
+| `workflow_bundle.json` | JSON | Run create | Bundled workflow input used to restart the run without re-reading the original workflow files. Includes the root workflow path plus bundled child workflow sources and inline files. |
| `final.patch` | Diff | Run end | Git diff from `base_sha` to final HEAD. Only present in git checkpoint mode. |
-| `retro.json` | JSON | Run end | Post-run retrospective analysis — `smoothness_rating`, `learnings`, `friction_points`, `stages`. Omitted if `--no-retro` is passed. See [Retros](/execution/retros). |
-| `cli.log` | Text | Continuous | Per-run tracing log. Contains the same tracing output as the daily log file, scoped to this run. |
+| `run.pid` | Text | Legacy only | Legacy process ID file from older runs. Current detached launches use launcher records instead, and current attach/resume no longer read `run.pid`. |
-## `nodes/` subdirectory
+## Local-only directories
-Each node execution writes artifacts into `nodes/{node_id}/`. When a node is retried, subsequent visits use `nodes/{node_id}-visit_{N}/` (where N starts at 2).
+These paths are local runtime state and caches, not the canonical run record.
-Every node gets a `status.json` after completion containing `status`, `notes`, `failure_reason`, and `timestamp`. The remaining files depend on the handler type:
+- **`worktree/`** — When running in worktree mode, Fabro creates a Git worktree here as the working directory for agents and commands.
+- **`runtime/`** — Local runtime files. Today this is mainly materialized blob payloads under `runtime/blobs/` and worker stderr logs for server-managed subprocesses.
+- **`cache/artifacts/files/`** — Captured artifact files organized by node and retry, plus a `manifest.json` for each retry directory.
+- **`nodes/{manager_node}_{visit}/child/`** — Nested scratch directories for manager-loop child workflows.
-**Agent and prompt nodes:**
+Large durable values, event streams, checkpoints, conclusions, and retros are no longer projected into live scratch by default. Use `fabro logs`, `fabro inspect`, the API, or `fabro store dump` for those surfaces.
-| File | Description |
-|---|---|
-| `prompt.md` | The full prompt sent to the LLM |
-| `response.md` | The LLM's response text |
-| `status.json` | Execution status with routing outcome |
+## Reconstructed and export-only layouts
-**Command nodes:**
+Some file names you may have seen in older runs or older docs still exist in reconstructed metadata branches or `fabro store dump` exports:
-| File | Description |
-|---|---|
-| `script_invocation.json` | Command metadata — `command`, `language`, `timeout_ms` |
-| `stdout.log` | Standard output |
-| `stderr.log` | Standard error |
-| `script_timing.json` | Timing info — `duration_ms`, `exit_code`, `timed_out` |
-
-**Nodes with git checkpointing:**
-
-| File | Description |
-|---|---|
-| `diff.patch` | Git diff of changes made during this stage |
-
-**Manager loop nodes:**
-
-Manager nodes that run sub-workflows write a nested `child/` directory containing a full run structure (run.json, start.json, checkpoint, nodes, etc.).
-
-## Other directories
-
-**`worktree/`** — When running in git checkpoint mode, Fabro creates a Git worktree here as the working directory for agents and commands.
-
-**`runtime/`** — Local-only runtime files such as materialized blob payloads under `runtime/blobs/`.
-
-**`cache/`** — Local filesystem cache for file-backed artifacts and captured test artifacts:
-
-- `cache/artifacts/values/` — large context values offloaded to file-backed artifacts
-- `cache/artifacts/files/` — captured test artifacts organized by node and retry
+- `run.json`, `start.json`, and `checkpoint.json` on metadata branches for rewind and fork
+- `run.json`, `start.json`, `checkpoint.json`, `conclusion.json`, `retro.json`, and `events.jsonl` in `fabro store dump` output
+- Per-node prompt, response, status, stdout, and stderr files in `fabro store dump` output and metadata rebuilds
## Browsing runs
@@ -89,28 +55,16 @@ fabro ps --filter workflow=my-workflow
## Full directory tree
```
-~/.fabro/scratch/
+~/.fabro/storage/scratch/
├── 20260307-01JQXYZ123ABC456DEF789/ # One directory per run
-│ ├── run.json
-│ ├── start.json
-│ ├── workflow.fabro
+│ ├── workflow_bundle.json
│ ├── run.pid # Legacy only; older runs may contain this
-│ ├── workflow.toml
-│ ├── progress.jsonl
-│ ├── live.json
-│ ├── checkpoint.json
-│ ├── conclusion.json
│ ├── final.patch
-│ ├── retro.json
-│ ├── cli.log
│ ├── runtime/
│ │ └── blobs/
│ │ └── 01JT5Y3KJ0N5S9E1Y7YFBR2G4D.json
│ ├── cache/
│ │ └── artifacts/
-│ │ ├── values/
-│ │ │ ├── response.plan.json
-│ │ │ └── command.output.json
│ │ └── files/
│ │ └── test/
│ │ └── retry_1/
@@ -118,32 +72,11 @@ fabro ps --filter workflow=my-workflow
│ │ │ └── screenshot.png
│ │ └── manifest.json
│ ├── nodes/
-│ │ ├── plan/
-│ │ │ ├── prompt.md
-│ │ │ ├── response.md
-│ │ │ └── status.json
-│ │ ├── work/
-│ │ │ ├── prompt.md
-│ │ │ ├── response.md
-│ │ │ ├── status.json
-│ │ │ └── diff.patch
-│ │ ├── work-visit_2/ # Retry of "work" node
-│ │ │ ├── prompt.md
-│ │ │ ├── response.md
-│ │ │ ├── status.json
-│ │ │ └── diff.patch
-│ │ ├── test/
-│ │ │ ├── script_invocation.json
-│ │ │ ├── stdout.log
-│ │ │ ├── stderr.log
-│ │ │ ├── script_timing.json
-│ │ │ └── status.json
│ │ └── manager/
-│ │ ├── status.json
│ │ └── child/
-│ │ ├── run.json
-│ │ ├── start.json
-│ │ ├── checkpoint.json
-│ │ └── nodes/
+│ │ ├── workflow_bundle.json
+│ │ ├── runtime/
+│ │ ├── cache/
+│ │ └── worktree/
│ └── worktree/ # Git worktree (git checkpoint mode)
```
diff --git a/lib/crates/fabro-cli/src/args.rs b/lib/crates/fabro-cli/src/args.rs
index 2ef6d0f76..370ceb1a2 100644
--- a/lib/crates/fabro-cli/src/args.rs
+++ b/lib/crates/fabro-cli/src/args.rs
@@ -219,7 +219,7 @@ pub(crate) struct RunFilterArgs {
#[arg(long = "label", value_name = "KEY=VALUE")]
pub(crate) label: Vec,
- /// Include orphan directories (no run.json)
+ /// Include orphan directories (no matching durable run)
#[arg(long)]
pub(crate) orphans: bool,
}
diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs
index e682bb519..7f852a594 100644
--- a/lib/crates/fabro-cli/src/commands/run/attach.rs
+++ b/lib/crates/fabro-cli/src/commands/run/attach.rs
@@ -365,9 +365,10 @@ fn infer_storage_dir(run_dir: &Path) -> Option {
#[cfg(test)]
fn infer_run_id(run_dir: &Path) -> Option {
- std::fs::read_to_string(run_dir.join("id.txt"))
- .ok()
- .map(|run_id| run_id.trim().to_string())
+ run_dir
+ .file_name()
+ .map(|name| name.to_string_lossy().to_string())
+ .and_then(|name| name.rsplit('-').next().map(ToOwned::to_owned))
.filter(|run_id| !run_id.is_empty())
.and_then(|run_id| run_id.parse().ok())
}
@@ -461,18 +462,16 @@ mod tests {
}
#[test]
- fn infer_run_id_reads_id_txt() {
+ fn infer_run_id_reads_run_dir_suffix() {
let dir = tempfile::tempdir().unwrap();
let storage_dir = dir.path().join("storage");
- let run_dir = storage_dir.join("scratch").join("20260401-test");
+ let run_id = fabro_types::fixtures::RUN_1;
+ let run_dir = storage_dir
+ .join("scratch")
+ .join(format!("20260401-{run_id}"));
std::fs::create_dir_all(&run_dir).unwrap();
- std::fs::write(
- run_dir.join("id.txt"),
- format!("{}\n", fabro_types::fixtures::RUN_1),
- )
- .unwrap();
- assert_eq!(infer_run_id(&run_dir), Some(fabro_types::fixtures::RUN_1));
+ assert_eq!(infer_run_id(&run_dir), Some(run_id));
}
#[test]
diff --git a/lib/crates/fabro-cli/tests/it/cmd/attach.rs b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
index 16dd2f592..4128fa1f4 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/attach.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/attach.rs
@@ -363,10 +363,6 @@ fn attach_replays_from_store_without_run_json_or_progress_jsonl() {
.assert()
.success();
- let run = resolve_run(&context, &run_id);
- let _ = std::fs::remove_file(run.run_dir.join("run.json"));
- let _ = std::fs::remove_file(run.run_dir.join("progress.jsonl"));
-
let mut cmd = context.command();
cmd.args(["attach", &run_id]);
cmd.timeout(SHARED_DAEMON_TIMEOUT);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/diff.rs b/lib/crates/fabro-cli/tests/it/cmd/diff.rs
index d17697267..791ce66c8 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/diff.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/diff.rs
@@ -137,7 +137,6 @@ fn diff_node_outputs_specific_patch() {
fn diff_node_reads_store_patch_without_disk_file() {
let context = test_context!();
let setup = setup_git_backed_changed_run(&context);
- let _ = std::fs::remove_file(setup.run.run_dir.join("nodes/step_one/diff.patch"));
let mut cmd = context.command();
cmd.args(["diff", &setup.run.run_id, "--node", "step_one"]);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
index 1cd80169a..d86eb8f09 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/inspect.rs
@@ -138,15 +138,6 @@ fn inspect_json_omits_run_dir() {
fn inspect_completed_run_reads_store_without_disk_metadata_files() {
let context = test_context!();
let run = setup_completed_fast_dry_run(&context);
- for name in [
- "run.json",
- "start.json",
- "conclusion.json",
- "checkpoint.json",
- "sandbox.json",
- ] {
- let _ = std::fs::remove_file(run.run_dir.join(name));
- }
let output = run_success(&context, &["inspect", &run.run_id]);
assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r#"
diff --git a/lib/crates/fabro-cli/tests/it/cmd/logs.rs b/lib/crates/fabro-cli/tests/it/cmd/logs.rs
index 89fc713ca..08749487e 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/logs.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/logs.rs
@@ -108,7 +108,6 @@ fn logs_completed_run_outputs_raw_ndjson() {
fn logs_completed_run_reads_store_without_progress_jsonl() {
let context = test_context!();
let run = setup_completed_dry_run(&context);
- let _ = std::fs::remove_file(run.run_dir.join("progress.jsonl"));
let mut filters = context.filters();
filters.push((
diff --git a/lib/crates/fabro-cli/tests/it/cmd/pr_create.rs b/lib/crates/fabro-cli/tests/it/cmd/pr_create.rs
index bc5030db3..eea4e179a 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/pr_create.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/pr_create.rs
@@ -67,9 +67,6 @@ fn pr_create_completed_dry_run_without_run_branch_errors() {
fn pr_create_uses_store_run_record_without_run_json() {
let context = test_context!();
let run = setup_completed_fast_dry_run(&context);
- let _ = std::fs::remove_file(run.run_dir.join("run.json"));
- let _ = std::fs::remove_file(run.run_dir.join("start.json"));
- let _ = std::fs::remove_file(run.run_dir.join("conclusion.json"));
let mut cmd = context.command();
cmd.args(["pr", "create", &run.run_id]);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/pr_view.rs b/lib/crates/fabro-cli/tests/it/cmd/pr_view.rs
index 9698c3d5b..771101196 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/pr_view.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/pr_view.rs
@@ -113,11 +113,6 @@ fn pr_view_reads_pull_request_from_store_without_pull_request_json() {
.unwrap();
});
- let pr_path = run.run_dir.join("pull_request.json");
- if pr_path.exists() {
- std::fs::remove_file(pr_path).unwrap();
- }
-
let mut cmd = context.command();
cmd.args(["pr", "view", &run.run_id]);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/ps.rs b/lib/crates/fabro-cli/tests/it/cmd/ps.rs
index c4aba189d..354935a4b 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/ps.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/ps.rs
@@ -27,7 +27,7 @@ fn help() {
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
--workflow Filter by workflow name (substring match)
--label Filter by label (KEY=VALUE, repeatable, AND semantics)
- --orphans Include orphan directories (no run.json)
+ --orphans Include orphan directories (no matching durable run)
--verbose Enable verbose output [env: FABRO_VERBOSE=]
-a, --all Show all runs, not just running (like docker ps -a)
-q, --quiet Only display run IDs
diff --git a/lib/crates/fabro-cli/tests/it/cmd/resume.rs b/lib/crates/fabro-cli/tests/it/cmd/resume.rs
index d643eaab3..aad477a14 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/resume.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/resume.rs
@@ -73,7 +73,6 @@ fn resume_rewound_run_succeeds() {
&setup.repo_dir,
&["rev-parse", &format!("fabro/run/{}", setup.run.run_id)],
);
- let _ = std::fs::remove_file(setup.run.run_dir.join("run.json"));
let mut resume_cmd = context.command();
resume_cmd.current_dir(&setup.repo_dir);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/rewind.rs b/lib/crates/fabro-cli/tests/it/cmd/rewind.rs
index 67a51207c..7b1022448 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/rewind.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/rewind.rs
@@ -85,7 +85,6 @@ fn rewind_target_updates_metadata_and_resume_hint() {
let mut cmd = context.command();
cmd.current_dir(&setup.repo_dir);
- let _ = std::fs::remove_file(setup.run.run_dir.join("run.json"));
cmd.args(["rewind", &setup.run.run_id, "@1", "--no-push"]);
let (snapshot, output) = run_and_format(&mut cmd, &git_filters(&context));
diff --git a/lib/crates/fabro-cli/tests/it/cmd/rm.rs b/lib/crates/fabro-cli/tests/it/cmd/rm.rs
index 1ce9f1153..3452baef3 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/rm.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/rm.rs
@@ -127,7 +127,6 @@ fn rm_force_deletes_submitted_run() {
fn rm_force_deletes_run_without_sandbox_json_when_store_has_sandbox() {
let context = test_context!();
let setup = setup_local_sandbox_run(&context);
- let _ = std::fs::remove_file(setup.run.run_dir.join("sandbox.json"));
let mut filters = context.filters();
filters.push((
@@ -146,7 +145,7 @@ fn rm_force_deletes_run_without_sandbox_json_when_store_has_sandbox() {
");
assert!(
!setup.run.run_dir.exists(),
- "run directory should be deleted even without sandbox.json"
+ "run directory should be deleted"
);
}
diff --git a/lib/crates/fabro-cli/tests/it/cmd/run.rs b/lib/crates/fabro-cli/tests/it/cmd/run.rs
index 1a875ade5..6bdc03d3a 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/run.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/run.rs
@@ -1333,13 +1333,11 @@ fn detach_creates_run_dir_with_detach_log() {
serde_json::json!({
"run_dir": run_dir,
"launcher_log_exists": context.storage_dir.join("launchers").join(format!("{run_id}.log")).exists(),
- "detach_log_exists": run_dir.join("detach.log").exists(),
}),
@r#"
{
"run_dir": "[RUN_DIR]",
- "launcher_log_exists": false,
- "detach_log_exists": false
+ "launcher_log_exists": false
}
"#
);
diff --git a/lib/crates/fabro-cli/tests/it/cmd/runner.rs b/lib/crates/fabro-cli/tests/it/cmd/runner.rs
index 47fc6780d..4763ea671 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/runner.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/runner.rs
@@ -4,7 +4,6 @@ use fabro_types::{EventBody, RunEvent};
use super::support::{run_events, run_state, server_target};
use crate::support::{fabro_json_snapshot, unique_run_id};
-use fabro_config::RunScratch;
const SHARED_DAEMON_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
@@ -446,9 +445,6 @@ fn detached_run_answers_pending_question_without_interview_scratch_files() {
);
let run_dir = context.find_run_dir(&run_id);
- let scratch = RunScratch::new(&run_dir);
- let legacy_interview_request = scratch.runtime_dir().join("interview_request.json");
- let legacy_interview_response = scratch.runtime_dir().join("interview_response.json");
let runtime = tokio::runtime::Runtime::new().expect("test runtime should build");
let question_id = runtime.block_on(async {
let (client, base_url) = server_endpoint(&context.storage_dir);
@@ -459,14 +455,6 @@ fn detached_run_answers_pending_question_without_interview_scratch_files() {
.to_string();
assert_eq!(question["stage"], "approve");
- assert!(
- !legacy_interview_request.exists(),
- "worker should not create interview_request.json"
- );
- assert!(
- !legacy_interview_response.exists(),
- "worker should not create interview_response.json"
- );
let response = client
.post(format!(
diff --git a/lib/crates/fabro-cli/tests/it/cmd/sandbox_cp.rs b/lib/crates/fabro-cli/tests/it/cmd/sandbox_cp.rs
index 197caa4eb..0b1f36d61 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/sandbox_cp.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/sandbox_cp.rs
@@ -73,7 +73,6 @@ fn sandbox_cp_downloads_file_from_run() {
fn sandbox_cp_downloads_file_from_store_without_sandbox_json() {
let context = test_context!();
let setup = setup_local_sandbox_run(&context);
- let _ = std::fs::remove_file(setup.run.run_dir.join("sandbox.json"));
let dest = context.temp_dir.join("downloaded-from-store.txt");
let mut cmd = context.cp();
cmd.args([
diff --git a/lib/crates/fabro-cli/tests/it/cmd/start.rs b/lib/crates/fabro-cli/tests/it/cmd/start.rs
index c278ee614..a988bef12 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/start.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/start.rs
@@ -102,9 +102,6 @@ fn start_by_run_id_starts_created_run_without_run_json_or_status_json() {
.assert()
.success();
- let run_dir = context.find_run_dir(&run_id);
- let _ = std::fs::remove_file(run_dir.join("run.json"));
-
context
.command()
.args(["start", &run_id])
diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs
index fe0145733..05062b4f8 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/support.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs
@@ -579,9 +579,6 @@ pub(crate) fn find_run_dir(storage_dir: &Path, run_id: &str) -> Option
}
fn infer_run_id(run_dir: &Path) -> String {
- if let Ok(id) = std::fs::read_to_string(run_dir.join("id.txt")) {
- return id.trim().to_string();
- }
run_dir
.file_name()
.map(|name| name.to_string_lossy().to_string())
diff --git a/lib/crates/fabro-cli/tests/it/cmd/system_prune.rs b/lib/crates/fabro-cli/tests/it/cmd/system_prune.rs
index ee2d1a616..dbc6078f1 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/system_prune.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/system_prune.rs
@@ -26,7 +26,7 @@ fn help() {
--workflow Filter by workflow name (substring match)
--label Filter by label (KEY=VALUE, repeatable, AND semantics)
--verbose Enable verbose output [env: FABRO_VERBOSE=]
- --orphans Include orphan directories (no run.json)
+ --orphans Include orphan directories (no matching durable run)
--older-than Only prune runs older than this duration (e.g. 24h, 7d). Default: 24h when no explicit filters are set
--yes Actually delete (default is dry-run)
-h, --help Print help
diff --git a/lib/crates/fabro-cli/tests/it/cmd/wait.rs b/lib/crates/fabro-cli/tests/it/cmd/wait.rs
index b9cc3e781..548879852 100644
--- a/lib/crates/fabro-cli/tests/it/cmd/wait.rs
+++ b/lib/crates/fabro-cli/tests/it/cmd/wait.rs
@@ -57,7 +57,6 @@ fn wait_completed_run_prints_success_summary() {
fn wait_completed_run_reads_store_without_status_or_conclusion_files() {
let context = test_context!();
let run = setup_completed_fast_dry_run(&context);
- let _ = std::fs::remove_file(run.run_dir.join("conclusion.json"));
let mut filters = context.filters();
filters.push((
r"\b\d+(\.\d+)?(ms|s)\b".to_string(),
diff --git a/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs b/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
index 7dfad0a2a..1413f2487 100644
--- a/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
+++ b/lib/crates/fabro-cli/tests/it/scenario/lifecycle.rs
@@ -134,16 +134,17 @@ fn dry_run_create_start_attach_works_with_default_run_lookup() {
.success();
let run_dir = context.find_run_dir(&run_id);
+ let state = run_state(&run_dir);
fabro_json_snapshot!(
context,
serde_json::json!({
- "run_json_exists": run_dir.join("run.json").exists(),
- "conclusion_json_exists": run_dir.join("conclusion.json").exists(),
+ "status": state.status.map(|status| status.status),
+ "has_conclusion": state.conclusion.is_some(),
}),
@r#"
{
- "run_json_exists": false,
- "conclusion_json_exists": false
+ "status": "succeeded",
+ "has_conclusion": true
}
"#
);
@@ -176,16 +177,17 @@ fn dry_run_detach_attach_works_with_default_run_lookup() {
.success();
let run_dir = context.find_run_dir(&run_id);
+ let state = run_state(&run_dir);
fabro_json_snapshot!(
context,
serde_json::json!({
"run_dir": run_dir,
- "conclusion_json_exists": run_dir.join("conclusion.json").exists(),
+ "has_conclusion": state.conclusion.is_some(),
}),
@r#"
{
"run_dir": "[RUN_DIR]",
- "conclusion_json_exists": false
+ "has_conclusion": true
}
"#
);
@@ -247,16 +249,17 @@ digraph BarBaz {
.success();
let run_dir = context.find_run_dir(&run_id);
+ let state = run_state(&run_dir);
fabro_json_snapshot!(
context,
serde_json::json!({
"run_dir_exists": run_dir.exists(),
- "conclusion_json_exists": run_dir.join("conclusion.json").exists(),
+ "has_conclusion": state.conclusion.is_some(),
}),
@r#"
{
"run_dir_exists": true,
- "conclusion_json_exists": false
+ "has_conclusion": true
}
"#
);
diff --git a/lib/crates/fabro-cli/tests/it/scenario/mod.rs b/lib/crates/fabro-cli/tests/it/scenario/mod.rs
index d28e171ce..40d77271c 100644
--- a/lib/crates/fabro-cli/tests/it/scenario/mod.rs
+++ b/lib/crates/fabro-cli/tests/it/scenario/mod.rs
@@ -27,15 +27,10 @@ fn block_on(future: impl std::future::Future