Skip to content

fix: a CLI run ends with the CLI, and list cells take pasted JSON - #52

Merged
ragnorc merged 3 commits into
mainfrom
fix/studio-sweep-3-20261031
Oct 31, 2026
Merged

ragnorc merged 3 commits into
mainfrom
fix/studio-sweep-3-20261031

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Three last items from the wider review that #50 and #51 left open. Based on main (6216514) and independent of #51.

A CLI run ends when the CLI does (graph_store)

A run read the CLI's output to the end before it waited for the CLI to exit. A program the CLI started that held its output open and outlived it (a credential helper's background process, for example) kept the run going until Studio's timeout. That held the one CLI turn for 30 s or more. The run now waits for the exit beside the reads. Once the CLI has ended, its pipes are read for 250 ms more, long enough to deliver what it wrote.

A list cell takes a list pasted in JSON (gq_language)

A list parameter read JSON (["a","b"], the form a list value is copied in) or comma-separated items. A list cell, form field or bulk edit read only the comma form, so a copied list pasted into a cell was refused. One reader, input::list_texts, now reads both for cells and parameters. It also covers [], so the special case for an empty list goes.

The gateway's held-clause rule beside the compiler's (agent_panel, test only)

The gateway decides from tokens alone whether a mutation is written as commit or as unattached branch, so that text the compiler can't parse isn't let through unread. Its test now pins two things: wherever the compiler's parse finds the clause, the gateway refuses; where neither finds it, both let the write through.

Testing

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1,166 passed) and the repo checks pass locally, on top of the current main.
  • cli_tokens::tests::input_a_command_does_not_read_ends_at_its_timeout (extended with a script that leaves a background process holding its output) and input::tests::a_list_reads_back_as_it_was_written (extended with pasted JSON) both fail without their fixes (checked).

Checked and left

  • Unused code: no pub function in the workspace is named only where it's defined, and no crate has an unused dependency. The one hit, studio_theme's util, is there for its debug-embed feature.
  • Coalescing settings writes: the writes arrive debounced at their sources, and the write thread's strict order is what read-edit-write jobs and the quit-time flush rely on.

Note

Medium Risk
Changes how Studio waits on Omnigraph CLI subprocess I/O (affects token export and CLI turn duration), plus shared list parsing for edits and parameters.

Overview
CLI subprocess handling no longer blocks until stdout/stderr pipes close. run_once races pipe reads against process exit; after the CLI exits, pipes are drained for 250 ms so output is captured without waiting on detached helpers (e.g. credential helpers). A regression test covers a script that backgrounds a long sleep while printing a token.

List property input is unified through new list_texts: bracket-leading text is parsed as JSON (as when copying a list value), otherwise comma-separated items as before. Form cells and query parameters both use this path; the separate empty-list shortcut in parse_input is removed. Tests cover pasted ["a", "b"] and reject null elements.

Gateway mutation tests only: held vs allowed as commit cases are named arrays, with assertions that held_off_the_branch (token scan) aligns with the compiler’s declared_write parse wherever both apply.

Reviewed by Cursor Bugbot for commit 13fb56b. Bugbot is set up for automated code reviews on this repo. Configure here.

…e its pipes

A run read the CLI's output to the end before it waited for the CLI to
exit. A program the CLI started that held its output open and outlived
it (a credential helper's background process) kept the run going until
Studio's timeout, holding the one CLI turn for 30 s or more. The run now
waits for the exit beside the reads. Once the CLI has ended, its pipes are
read for 250 ms more, long enough to deliver what it wrote.
A list parameter read JSON (`["a","b"]`, the form a list value is copied
in) or comma-separated items, but a list cell, form field or bulk edit
read only the comma form, so a copied list pasted into a cell was
refused. One reader, `input::list_texts`, reads both for cells and
parameters. It also covers `[]`, so the special case for an empty list
goes.
…iler finds the clause

The gateway decides from tokens alone whether a mutation is written `as
commit` or `as unattached branch`, so text the compiler cannot parse is
not let through unread. Its test now pins that, wherever the compiler's
parse finds the clause, the gateway refuses, and that where neither finds
it, both let the write through.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 13fb56b. Configure here.

let status = match exited {
Some(status) => status,
None => child.status().await,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pipes stay open past read bound

Medium Severity

A pipe that hits its read bound no longer closes. The Take wrappers now outlive the reads and stay open through child.status(), so a command that writes past the bound can fill the OS pipe and block. That holds the CLI turn until the outer timeout, instead of ending once the bound is read.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 13fb56b. Configure here.

@ragnorc
ragnorc merged commit 10b7786 into main Oct 31, 2026
8 checks passed
ragnorc added a commit that referenced this pull request Nov 1, 2026
…lists (#54)

* fix(cli): close each pipe at its read limit, and filters read pasted lists

A review of #52 found that its change kept a CLI's pipes open after
their read limit: the `Take` wrappers lived until the run ended. A CLI
that wrote more than its limit plus a pipe buffer (an oversized token)
then blocked on a full pipe and never exited, so the run ran out the
timeout instead of being refused at once, holding the CLI turn. Each read
now owns its pipe and closes it at the limit, keeping what it read.

The test of a program left holding the pipes sleeps a minute and allows
30 s, with room for macOS to scan a new script on its first run.

The "is one of" filter says it reads a list as a list cell does, but it
kept the comma-only reader when cells took JSON. It now uses the same
`list_texts`, so a copied list pasted into a filter works too.

* fix(cli): say an over-long token is too long, and leave pasted lists uncompleted

An export answer longer than a token fills the read bound without its newline and
is now refused as too long, instead of as whatever the closed pipe made of it. The
leaving-process test gets room to fail on a slow runner without timing out. A filter
list typed as JSON is no longer completed, since a pick would rewrite it as items.
ragnorc added a commit that referenced this pull request Nov 1, 2026
…lists

A review of #52 found that its change kept a CLI's pipes open after
their read limit: the `Take` wrappers lived until the run ended. A CLI
that wrote more than its limit plus a pipe buffer (an oversized token)
then blocked on a full pipe and never exited, so the run ran out the
timeout instead of being refused at once, holding the CLI turn. Each read
now owns its pipe and closes it at the limit, keeping what it read.

The test of a program left holding the pipes sleeps a minute and allows
30 s, with room for macOS to scan a new script on its first run.

The "is one of" filter says it reads a list as a list cell does, but it
kept the comma-only reader when cells took JSON. It now uses the same
`list_texts`, so a copied list pasted into a filter works too.
ragnorc added a commit that referenced this pull request Nov 1, 2026
…lists

A review of #52 found that its change kept a CLI's pipes open after
their read limit: the `Take` wrappers lived until the run ended. A CLI
that wrote more than its limit plus a pipe buffer (an oversized token)
then blocked on a full pipe and never exited, so the run ran out the
timeout instead of being refused at once, holding the CLI turn. Each read
now owns its pipe and closes it at the limit, keeping what it read.

The test of a program left holding the pipes sleeps a minute and allows
30 s, with room for macOS to scan a new script on its first run.

The "is one of" filter says it reads a list as a list cell does, but it
kept the comma-only reader when cells took JSON. It now uses the same
`list_texts`, so a copied list pasted into a filter works too.
ragnorc added a commit that referenced this pull request Nov 1, 2026
…lists

A review of #52 found that its change kept a CLI's pipes open after
their read limit: the `Take` wrappers lived until the run ended. A CLI
that wrote more than its limit plus a pipe buffer (an oversized token)
then blocked on a full pipe and never exited, so the run ran out the
timeout instead of being refused at once, holding the CLI turn. Each read
now owns its pipe and closes it at the limit, keeping what it read.

The test of a program left holding the pipes sleeps a minute and allows
30 s, with room for macOS to scan a new script on its first run.

The "is one of" filter says it reads a list as a list cell does, but it
kept the comma-only reader when cells took JSON. It now uses the same
`list_texts`, so a copied list pasted into a filter works too.
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