feat(cli): rename store dump to dump

This commit is contained in:
Bryan Helmkamp 2026-04-23 07:48:03 -04:00
parent 7fd8f5a57b
commit cb9b762119
No known key found for this signature in database
19 changed files with 77 additions and 136 deletions

View file

@ -33,7 +33,7 @@ These paths are local runtime state, not canonical event projections.
These names are still real, but they are no longer live scratch files by default:
- 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
- `fabro 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

View file

@ -7,7 +7,7 @@ When an agent or prompt node finishes, Fabro captures its response text and prod
## Response capture
After an agent or prompt node completes, Fabro captures the full response text and persists it to `stages/{node_id}@{visit}/response.md` in metadata snapshots and `fabro store dump` output. It also writes the final outcome (status, context updates, routing directives) to `stages/{node_id}@{visit}/status.json`.
After an agent or prompt node completes, Fabro captures the full response text and persists it to `stages/{node_id}@{visit}/response.md` in metadata snapshots and `fabro dump` output. It also writes the final outcome (status, context updates, routing directives) to `stages/{node_id}@{visit}/status.json`.
## Context updates
@ -92,7 +92,7 @@ review -> approve [label="Approve"]
## Output logging
Fabro writes several files per stage to `stages/{node_id}@{visit}/` in metadata snapshots and `fabro store dump` output:
Fabro writes several files per stage to `stages/{node_id}@{visit}/` in metadata snapshots and `fabro dump` output:
| File | Contents |
|---|---|

View file

@ -295,4 +295,4 @@ Use prompt nodes for analysis, classification, and summarization tasks where too
## Prompt logging
Fabro persists the assembled prompt to `stages/{node_id}@{visit}/prompt.md` in metadata snapshots and `fabro store dump` output for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
Fabro persists the assembled prompt to `stages/{node_id}@{visit}/prompt.md` in metadata snapshots and `fabro dump` output for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.

View file

@ -81,7 +81,7 @@ jq '{from: .properties.from_node, to: .properties.to_node, label: .properties.la
<(fabro logs 01JKXYZ...) | head
```
If you need files on disk for offline analysis, `fabro store dump` exports `events.jsonl` plus run-state projections.
If you need files on disk for offline analysis, `fabro dump` exports `events.jsonl` plus run-state projections.
## Event categories
@ -132,6 +132,6 @@ Post-run analysis surfaces include:
|---|---|
| `fabro logs <RUN>` | Full event envelope stream as NDJSON |
| `fabro inspect <RUN>` | Current durable run state, including run/start/checkpoint/conclusion records |
| `fabro store dump --output <DIR> <RUN>` | Exported `events.jsonl` plus reconstructed JSON and node files |
| `fabro dump --output <DIR> <RUN>` | 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.

View file

@ -143,4 +143,4 @@ Retros are also available via the REST API. See the [list retros](/api-reference
## Storage
Retros are stored in durable run state. If you need files on disk, `fabro store dump` materializes retro text under `stages/retro/` alongside `run.json`, stage files, and the rest of the exported run data.
Retros are stored in durable run state. If you need files on disk, `fabro dump` materializes retro text under `stages/retro/` alongside `run.json`, stage files, and the rest of the exported run data.

View file

@ -947,13 +947,13 @@ fabro secret rm ANTHROPIC_API_KEY
---
## `fabro store dump`
## `fabro dump`
Export the contents of a run's store-backed state to a directory for debugging and inspection.
```bash
fabro store dump <RUN>
fabro store dump abc123 -o ./debug-output
fabro dump <RUN>
fabro dump abc123 -o ./debug-output
```
| Argument / Flag | Description |

View file

@ -30,18 +30,18 @@ These paths are local runtime state and caches, not the canonical run state.
- **`runtime/`** — Local runtime files. Today this is mainly materialized blob payloads under `runtime/blobs/`.
- **`nodes/{manager_node}_{visit}/child/`** — Nested scratch directories for manager-loop child workflows.
Large durable values, event streams, checkpoints, diffs, 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.
Large durable values, event streams, checkpoints, diffs, conclusions, and retros are no longer projected into live scratch by default. Use `fabro logs`, `fabro inspect`, the API, or `fabro dump` for those surfaces.
## Reconstructed and export-only layouts
Reconstructed metadata branches and `fabro store dump` exports now use the same core layout:
Reconstructed metadata branches and `fabro dump` exports now use the same core layout:
- `run.json` for the current projection snapshot, including the current checkpoint
- `graph.fabro` for workflow source
- `stages/retro/*.md` for retro prompt/response text
- `stages/{node_id}@{visit}/...` for per-stage prompt, response, status, diff, stdout, and stderr files
`fabro store dump` adds export-only history surfaces on top of that shared layout:
`fabro dump` adds export-only history surfaces on top of that shared layout:
- `events.jsonl` for the durable event stream
- `checkpoints/*.json` for checkpoint history snapshots

View file

@ -514,7 +514,7 @@ pub(crate) struct InspectArgs {
}
#[derive(Args)]
pub(crate) struct StoreDumpArgs {
pub(crate) struct DumpArgs {
#[command(flatten)]
pub(crate) server: ServerTargetArgs,
@ -1001,8 +1001,8 @@ pub(crate) enum Commands {
Parse(ParseArgs),
/// Inspect and copy run artifacts (screenshots, reports, traces)
Artifact(ArtifactNamespace),
/// Export store-backed run state for debugging
Store(StoreNamespace),
/// Export a run's durable state to a directory
Dump(DumpArgs),
#[command(flatten)]
RunsCmd(RunsCommands),
/// List and test LLM models
@ -1085,9 +1085,7 @@ impl Commands {
ArtifactCommand::List(_) => "artifact list",
ArtifactCommand::Cp(_) => "artifact cp",
},
Self::Store(ns) => match &ns.command {
StoreCommand::Dump(_) => "store dump",
},
Self::Dump(_) => "dump",
Self::Exec(_) => "exec",
Self::RunCmd(cmd) => cmd.name(),
Self::Preflight(_) => "preflight",
@ -1199,18 +1197,6 @@ pub(crate) enum ArtifactCommand {
Cp(ArtifactCpArgs),
}
#[derive(Args)]
pub(crate) struct StoreNamespace {
#[command(subcommand)]
pub(crate) command: StoreCommand,
}
#[derive(Subcommand)]
pub(crate) enum StoreCommand {
/// Export a run's durable state to a directory
Dump(StoreDumpArgs),
}
#[derive(Args)]
pub(crate) struct SecretNamespace {
#[command(flatten)]

View file

@ -1,6 +1,6 @@
#![expect(
clippy::disallowed_methods,
reason = "CLI `store dump` command: sync file I/O for dump outputs"
reason = "CLI `dump` command: sync file I/O for dump outputs"
)]
use std::io::ErrorKind;
@ -21,13 +21,13 @@ use serde::de::DeserializeOwned;
use tokio::task::spawn_blocking;
use super::run_export::StoreRunExport;
use crate::args::StoreDumpArgs;
use crate::args::DumpArgs;
use crate::command_context::CommandContext;
use crate::server_client::Client;
use crate::shared::{absolute_or_current, print_json_pretty};
pub(crate) async fn dump_command(
args: &StoreDumpArgs,
args: &DumpArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,

View file

@ -1,21 +1,3 @@
pub(crate) mod dump;
pub(crate) mod rebuild;
mod run_export;
use anyhow::Result;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
use crate::args::{StoreCommand, StoreNamespace};
pub(crate) async fn dispatch(
ns: StoreNamespace,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
match ns.command {
StoreCommand::Dump(args) => dump::dump_command(&args, cli, cli_layer, printer).await,
}
}

View file

@ -247,8 +247,9 @@ async fn main_inner() -> (String, Result<()>) {
Commands::Artifact(ns) => {
commands::artifact::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
}
Commands::Store(ns) => {
commands::store::dispatch(ns, &cli_settings, &cli_layer, printer).await?;
Commands::Dump(args) => {
commands::store::dump::dump_command(&args, &cli_settings, &cli_layer, printer)
.await?;
}
Commands::RunsCmd(cmd) => {
commands::runs::dispatch(cmd, &cli_settings, &cli_layer, printer).await?;
@ -505,7 +506,7 @@ async fn prepare_server_bootstrap(
mod tests {
use args::{
AuthCommand, AuthNamespace, Commands, InstallGitHubStrategyArg, ModelsCommand,
ProviderCommand, ProviderNamespace, StoreCommand, StoreNamespace,
ProviderCommand, ProviderNamespace,
};
use tokio::runtime::Runtime;
@ -940,13 +941,11 @@ level = "warn"
}
#[test]
fn parse_store_dump_command() {
let cli = Cli::try_parse_from(["fabro", "store", "dump", "ABC123", "-o", "./out"])
.expect("should parse");
fn parse_dump_command() {
let cli =
Cli::try_parse_from(["fabro", "dump", "ABC123", "-o", "./out"]).expect("should parse");
match *cli.command.unwrap() {
Commands::Store(StoreNamespace {
command: StoreCommand::Dump(args),
}) => {
Commands::Dump(args) => {
assert_eq!(args.run, "ABC123");
assert_eq!(args.output, std::path::PathBuf::from("./out"));
}

View file

@ -16,14 +16,14 @@ use crate::support::{LightweightCli, unique_run_id};
fn help() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["store", "dump", "--help"]);
cmd.args(["dump", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
Export a run's durable state to a directory
Usage: fabro store dump [OPTIONS] --output <OUTPUT> <RUN>
Usage: fabro dump [OPTIONS] --output <OUTPUT> <RUN>
Arguments:
<RUN> Run ID prefix or workflow name
@ -42,7 +42,27 @@ fn help() {
}
#[test]
fn store_dump_accepts_server_target_from_separate_home() {
fn old_store_dump_command_is_rejected() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["store", "dump", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: unrecognized subcommand 'store'
tip: some similar subcommands exist: 'server', 'secret', 'system', 'start'
Usage: fabro [OPTIONS] [COMMAND]
For more information, try '--help'.
");
}
#[test]
fn dump_accepts_server_target_from_separate_home() {
let context = test_context!();
let run = setup_completed_dry_run(&context);
let cli = LightweightCli::new();
@ -51,7 +71,6 @@ fn store_dump_accepts_server_target_from_separate_home() {
let mut cmd = cli.command();
cmd.args([
"store",
"dump",
"--server",
&server,
@ -63,10 +82,10 @@ fn store_dump_accepts_server_target_from_separate_home() {
cmd.env("FABRO_DEV_TOKEN", dev_token);
}
let output = cmd.output().expect("store dump should execute");
let output = cmd.output().expect("dump should execute");
assert!(
output.status.success(),
"store dump via remote server target failed\nstdout:\n{}\nstderr:\n{}",
"dump via remote server target failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
@ -74,7 +93,7 @@ fn store_dump_accepts_server_target_from_separate_home() {
}
#[test]
fn store_dump_exports_large_command_output_backed_by_blob_refs() {
fn dump_exports_large_command_output_backed_by_blob_refs() {
let context = test_context!();
let workflow = context.temp_dir.join("large-output.fabro");
fs::write(
@ -130,17 +149,11 @@ fn store_dump_exports_large_command_output_backed_by_blob_refs() {
let output_dir = context.temp_dir.join("export");
let mut dump_cmd = context.command();
dump_cmd.args([
"store",
"dump",
"--output",
output_dir.to_str().unwrap(),
&run_id,
]);
let dump_output = dump_cmd.output().expect("store dump should execute");
dump_cmd.args(["dump", "--output", output_dir.to_str().unwrap(), &run_id]);
let dump_output = dump_cmd.output().expect("dump should execute");
assert!(
dump_output.status.success(),
"store dump failed\nstdout:\n{}\nstderr:\n{}",
"dump failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&dump_output.stdout),
String::from_utf8_lossy(&dump_output.stderr)
);
@ -153,7 +166,7 @@ fn store_dump_exports_large_command_output_backed_by_blob_refs() {
}
#[test]
fn store_dump_exports_blob_refs_and_artifacts_together() {
fn dump_exports_blob_refs_and_artifacts_together() {
let context = test_context!();
let workspace_dir = context.temp_dir.join("mixed-export");
fs::create_dir_all(&workspace_dir).unwrap();
@ -233,17 +246,11 @@ include = ["assets/**"]
let output_dir = context.temp_dir.join("export-mixed");
let mut dump_cmd = context.command();
dump_cmd.args([
"store",
"dump",
"--output",
output_dir.to_str().unwrap(),
&run_id,
]);
let dump_output = dump_cmd.output().expect("store dump should execute");
dump_cmd.args(["dump", "--output", output_dir.to_str().unwrap(), &run_id]);
let dump_output = dump_cmd.output().expect("dump should execute");
assert!(
dump_output.status.success(),
"store dump failed\nstdout:\n{}\nstderr:\n{}",
"dump failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&dump_output.stdout),
String::from_utf8_lossy(&dump_output.stderr)
);
@ -260,14 +267,13 @@ include = ["assets/**"]
}
#[test]
fn store_dump_exports_completed_run_snapshot() {
fn dump_exports_completed_run_snapshot() {
let context = test_context!();
let run = setup_completed_dry_run(&context);
let output_dir = context.temp_dir.join("export");
let mut cmd = context.command();
cmd.args([
"store",
"dump",
"--output",
output_dir.to_str().unwrap(),
@ -298,7 +304,7 @@ fn store_dump_exports_completed_run_snapshot() {
}
#[test]
fn store_dump_rejects_non_empty_output_dir() {
fn dump_rejects_non_empty_output_dir() {
let context = test_context!();
let run = setup_completed_dry_run(&context);
let output_dir = context.temp_dir.join("nonempty");
@ -307,7 +313,6 @@ fn store_dump_rejects_non_empty_output_dir() {
let mut cmd = context.command();
cmd.args([
"store",
"dump",
"--output",
output_dir.to_str().unwrap(),

View file

@ -25,7 +25,7 @@ fn help() {
validate Validate a workflow
graph Render a workflow graph as SVG
artifact Inspect and copy run artifacts (screenshots, reports, traces)
store Export store-backed run state for debugging
dump Export a run's durable state to a directory
rm Remove one or more workflow runs
inspect Show detailed information about a workflow run
archive Mark terminal runs as archived (reviewed, no further action needed). Archived runs are hidden from default listings

View file

@ -9,6 +9,7 @@ mod diff;
mod discord;
mod docs;
mod doctor;
mod dump;
mod exec;
mod fabro;
mod fork;
@ -53,8 +54,6 @@ mod server_start;
mod server_status;
mod server_stop;
mod start;
mod store;
mod store_dump;
pub(crate) mod support;
mod system;
mod system_df;

View file

@ -1,29 +0,0 @@
use fabro_test::{fabro_snapshot, test_context};
#[test]
fn help() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["store", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
Export store-backed run state for debugging
Usage: fabro store [OPTIONS] <COMMAND>
Commands:
dump Export a run's durable state to a directory
help Print this message or the help of the given subcommand(s)
Options:
--json Output as JSON [env: FABRO_JSON=]
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
--quiet Suppress non-essential output [env: FABRO_QUIET=]
--verbose Enable verbose output [env: FABRO_VERBOSE=]
-h, --help Print help
----- stderr -----
");
}

View file

@ -6,8 +6,8 @@
use fabro_test::test_context;
use super::{
completed_nodes, find_run_dir, fixture, read_conclusion, run_id_for, sandbox_tests,
store_dump_export, timeout_for,
completed_nodes, dump_export, find_run_dir, fixture, read_conclusion, run_id_for,
sandbox_tests, timeout_for,
};
sandbox_tests!(command_agent_mixed, keys = ["ANTHROPIC_API_KEY"]);
@ -48,7 +48,7 @@ fn scenario_command_agent_mixed(sandbox: &str) {
"verify should be completed"
);
let export_dir = store_dump_export(&context, &run_id_for(&run_dir));
let export_dir = dump_export(&context, &run_id_for(&run_dir));
let stdout = std::fs::read_to_string(export_dir.join("stages/verify@1/stdout.log"))
.expect("verify stdout.log should exist");
assert!(

View file

@ -6,8 +6,8 @@
use fabro_test::test_context;
use super::{
completed_nodes, find_run_dir, fixture, read_conclusion, run_id_for, sandbox_tests,
store_dump_export, timeout_for,
completed_nodes, dump_export, find_run_dir, fixture, read_conclusion, run_id_for,
sandbox_tests, timeout_for,
};
sandbox_tests!(command_pipeline);
@ -47,7 +47,7 @@ fn scenario_command_pipeline(sandbox: &str) {
"step2 should be completed"
);
let export_dir = store_dump_export(&context, &run_id_for(&run_dir));
let export_dir = dump_export(&context, &run_id_for(&run_dir));
let stdout1 = std::fs::read_to_string(export_dir.join("stages/step1@1/stdout.log"))
.expect("step1 stdout.log should exist");
assert!(

View file

@ -6,8 +6,8 @@
use fabro_test::test_context;
use super::{
completed_nodes, find_run_dir, fixture, has_event, read_conclusion, read_run_spec, run_id_for,
sandbox_tests, store_dump_export, timeout_for,
completed_nodes, dump_export, find_run_dir, fixture, has_event, read_conclusion, read_run_spec,
run_id_for, sandbox_tests, timeout_for,
};
sandbox_tests!(full_stack, keys = ["ANTHROPIC_API_KEY"]);
@ -73,7 +73,7 @@ fn scenario_full_stack(sandbox: &str) {
}
// Verify node stdout should contain PASS
let export_dir = store_dump_export(&context, &run_id_for(&run_dir));
let export_dir = dump_export(&context, &run_id_for(&run_dir));
let stdout = std::fs::read_to_string(export_dir.join("stages/verify@1/stdout.log"))
.expect("verify stdout.log should exist");
assert!(

View file

@ -59,17 +59,16 @@ pub(super) fn has_event(run_dir: &Path, event_name: &str) -> bool {
.any(|event| event.event.event_name() == event_name)
}
pub(super) fn store_dump_export(context: &TestContext, run_id: &str) -> PathBuf {
let output_dir = context.temp_dir.join(format!("store-dump-{run_id}"));
pub(super) fn dump_export(context: &TestContext, run_id: &str) -> PathBuf {
let output_dir = context.temp_dir.join(format!("dump-{run_id}"));
context
.command()
.args([
"store",
"dump",
"--output",
output_dir
.to_str()
.expect("store dump output path should be valid UTF-8"),
.expect("dump output path should be valid UTF-8"),
run_id,
])
.assert()