Skip to content

bullpen sessions --json: read the coordination plane without scraping - #6

Merged
Steel-tech merged 1 commit into
mainfrom
feat/sessions-json-output
Aug 8, 2026
Merged

bullpen sessions --json: read the coordination plane without scraping#6
Steel-tech merged 1 commit into
mainfrom
feat/sessions-json-output

Conversation

@Steel-tech

@Steel-tech Steel-tech commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Closes the gap in #5: there was no machine-readable output anywhere in the CLI. A script that wanted to act on session state had two bad options — parse the fixed-width table, which truncates the session id to 8 chars, breaks on titles containing runs of spaces, and encodes parenthood as a └ child of glyph; or open the SQLite file directly and couple itself to the schema. bullpen sessions --json is a third option that is neither.

Decisions a reviewer would otherwise have to reverse-engineer

The wire shape is built in the CLI, not derived on the store type. sessions_json(&[Session]) -> Value hand-maps the record instead of #[derive(Serialize)] on bullpen_store::Session. That struct is internal state; deriving on it would make any future field rename a silent breaking change for every script consuming this output, with nothing in the store crate to signal that a rename is now a public-API change. The cost is a hand-maintained mirror — add a field to bullpen_llm::Usage and it will not appear in --json until it is added here — and that tradeoff is stated in the doc comment on the helper. Keeping the function pure is also what makes the empty case and the field mapping unit-testable without a process harness.

Absence is null, never a missing key. Every session object always carries all eleven keys. parent_session_id and pid are the two that go null. Consumers can index without existence checks; jq '.[].pid' never silently yields nothing on a top-level session.

Ids are full, not the table's 8-char prefix. The prefix is a display affordance; a prefix is not a key and can collide, so JSON emits the whole 36-char uuid. Same reasoning for usage, which is a nested {input_tokens, output_tokens} object rather than two flattened columns.

Errors are not wrapped in JSON. A store open or query failure still goes through anyhow to stderr with a non-zero exit. Emitting {"error": ...} on stdout with exit 0 would make failures indistinguishable from data for a caller that only checks whether stdout parses.

Order is list_sessions() order (updated_at DESC), unchanged. Output is pretty-printed with a trailing newline — readable when a human pipes it, irrelevant to a parser.

Scope

Additive. bullpen sessions with no flag is byte-for-byte what it was, including the no sessions yet hint. The diff inside fn sessions is additions only — the is_empty early-return and the entire for s in sessions printing block appear as unchanged context, no reborrow and no re-indentation. README gains one line under the command block.

Verification

Gate Result
cargo fmt --all --check clean
cargo test --workspace 94 passed, 0 failed (baseline 91; bin target 1 → 4)
cargo clippy --workspace --all-targets -- -D warnings silent

The three new tests cover the empty case, the complete element shape, and the null-vs-string behavior of parent_session_id.

The workspace has no CLI end-to-end harness, so the three requirements in the issue were also checked against the built binary rather than trusted to unit tests alone:

  • ./target/debug/bullpen sessions — same table, unchanged.
  • HOME=$(mktemp -d) ./target/debug/bullpen sessions --json[]; the same binary against the same empty HOME without --json still prints the prose hint.
  • ./target/debug/bullpen sessions --json | jq — valid, full 36-char uuid, usage nested, parent_session_id/pid present as null.

Known gap, not closed here

Nothing automatically guards the default human table output. Closing that would mean pulling in assert_cmd or trycmd — the first test-harness crate in a workspace that deliberately has none — so it is out of scope for this PR, but the guard genuinely does not exist.

Summary by CodeRabbit

  • New Features

    • Added a --json option to the bullpen sessions command for machine-readable session lists.
    • JSON output includes session identifiers, metadata, usage, relationships, status, and process IDs.
    • Human-readable output remains the default.
  • Tests

    • Added coverage for empty results and complete session and parent-session serialization.
  • Documentation

    • Updated command documentation with the new JSON output option.

The store is the coordination plane, but until now the only way to read it
from outside bullpen was to parse a padded column layout — which loses the
full session id (the table truncates to 8 chars), breaks on titles that
contain runs of spaces, and encodes parenthood as a `└ child of` glyph — or
to open the SQLite file directly and couple to the schema. `--json` gives
callers a stable surface that is neither.

The wire shape is built in the CLI by a pure `sessions_json(&[Session])`
rather than by deriving Serialize on `bullpen_store::Session`. The store
struct is internal state; deriving on it would turn any future field rename
into a silent breaking change for every script consuming this output. The
cost is a hand-maintained mirror — a new field on `Usage` will not appear in
`--json` until it is added here — which is noted on the helper.

Every key is always present; absence is null, never a missing key, so a
consumer can index without existence checks. `parent_session_id` and `pid`
are the two that go null.

Empty store emits `[]`, not the prose hint — a script that shells out gets
parseable output on every path. Errors keep flowing through anyhow to stderr
with a non-zero exit; they are deliberately not wrapped in JSON.

Additive: `bullpen sessions` with no flag is byte-for-byte what it was.

Verified: fmt, clippy -D warnings, and 94 workspace tests green (3 new,
covering the empty case, the full element shape, and the null-vs-string
parent field). Behavior checked against the built binary as well, since the
workspace has no CLI end-to-end harness — table output unchanged, and an
empty HOME prints `[]` with `--json` and the hint without it.

Refs #5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfAfAujueuZ3rDTiL9apx3
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b01b10bb-1a14-4a91-8cb1-edd20ca3c2d7

📥 Commits

Reviewing files that changed from the base of the PR and between 601bb64 and 2a880fe.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • README.md
  • crates/cli/Cargo.toml
  • crates/cli/src/main.rs

📝 Walkthrough

Walkthrough

The bullpen sessions command now accepts --json. It outputs session records as a formatted JSON array with metadata, usage, relationships, status, and process IDs. Human-readable output remains the default. Tests cover empty and populated session lists.

Changes

JSON session listing

Layer / File(s) Summary
JSON output flow
crates/cli/src/main.rs, crates/cli/Cargo.toml, README.md
The CLI wires the --json option into the sessions command, serializes complete session records, preserves the default text output, documents the option, and tests empty lists, full IDs and fields, and parent-session values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sessions-json-output

Comment @coderabbitai help to get the list of available commands.

@Steel-tech
Steel-tech merged commit 2089a64 into main Aug 8, 2026
5 checks passed
@Steel-tech
Steel-tech deleted the feat/sessions-json-output branch August 8, 2026 06:53
@Steel-tech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Pull request is closed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant