Skip to content

fix(writes): tell a write never sent from one whose answer was lost - #30

Merged
ragnorc merged 2 commits into
mainfrom
fix/write-not-sent-20261030
Oct 31, 2026
Merged

ragnorc merged 2 commits into
mainfrom
fix/write-not-sent-20261030

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #29: it is based on that branch and will retarget to main once #29 merges.

A write Studio never sent wrote nothing. One whose answer was lost may have landed. Studio now tells the two apart everywhere a write's outcome is shown.

What counts as never sent

graph_client::NoAnswer names why a request has no answer:

  • Retired: the connection was retired, because its CLI target is gone or names a transport Studio cannot use.
  • Unsendable: Studio would not make the request as asked, for example a path that leaves the server's root.
  • Unreached: the server refused the connection. This is judged by the error's kind, an io ConnectionRefused in the error chain, and never by its text. A request is written only on a connection that was made. A review traced reqwest, hyper-util, hyper and h2 and found no case where this follows a request the server processed.
  • Lost: any other transport failure, and any failure while reading the body after sending.

WriteOutcome::NotSent covers the first three. judge_write returns Answered<T>.

What each surface shows

  • Type table, Blob cells and query editor: "Not sent: . Nothing was written."
  • Review: a merge that was never sent reads "The merge was not sent: . Nothing was merged.", and a fix "Not sent: . Nothing was written." Neither is shown as the server's refusal, and neither leaves anything to settle. Before, a merge that failed to send read as an unknown outcome and kept Merge off until Studio could read the target's history. That read cannot succeed while the server is down.
  • Agent gateway: a request never sent is answered 403 forbidden if the target is retired or Studio would not make the request. It is answered 503 service_unavailable if the server could not be reached. Both carry effects: none. A write already retired is refused before its card is shown.

Said the same way everywhere

  • A refused connection is described by its kind as well as judged by it. On Windows (error 10061, whose text does not say "connection refused") it now reads "the connection was refused" as it does elsewhere. A failure that says "connection refused" only in its text keeps its own words and stays unknown.
  • A fix dropped because the one before it did not land, and a merge whose branch head could not be read, are not sent rather than refused.
  • A reason quoted into a sentence that goes on ends with one period, through studio_ui::clause. That covers the toast, which could read "… sent.. Nothing was written." for any message ending in a period, as well as the banners, the tooltips and the gateway's refusals.
  • A write never sent is logged with the words the agent gateway uses (graph_store::nothing_sent). The log still records it as a refusal in the constitution's sense: nothing was written.
  • A write and a Blob write share one send-and-judge step.

Testing

  • fmt, clippy -D warnings, cargo test --workspace (1,137 passed) and the repo checks, on this branch.
  • New tests:
    • graph_client a_write_the_server_refused_to_connect_to_is_not_sent: a refused connection, one worded as Windows words it, a reset, and an error that says "connection refused" only in its text.
    • studio_workspace a_refusal_toast_ends_its_message_once.
    • graph_store a_write_to_a_server_refusing_connections_is_not_sent: Studio's own reqwest transport against a closed port.
  • Extended tests: the gateway's a_writes_success_answer_says_what_it_did covers every NoAnswer, and the session, Blob, type table and retired-client tests cover NotSent.
  • Each new classification test fails without its fix (checked).
  • Specs 004, 007, 008, 013 and 016 state the rules.

Note

Medium Risk
Changes write-outcome semantics and agent-gateway HTTP responses across many surfaces; misclassification could wrongly tell users to check History or retry merges when nothing was sent, or vice versa.

Overview
Studio now classifies writes that never left the client separately from writes whose server answer was lost, and surfaces that consistently in the UI, session log, and agent gateway.

graph_client introduces NoAnswer (Retired, Unsendable, Unreached, Lost) on forward/send failures, maps the first three to WriteOutcome::NotSent, and splits server-response judgment into Answered (replacing WriteOutcome in judge_write). Connection refused is detected by ConnectionRefused I/O kind, not error text.

Agent gateway refuses writes to an already-retired target before approval; approved writes that fail to send get structured 403/503 CLI responses with "Nothing was sent." messaging via unsent_refusal / Reply::NotSent, instead of closing silently or treating everything as unknown.

UI and session (query editor, review merges/fixes, type table/Blob saves, activity toasts) handle NotSent as nothing written—no History settlement—distinct from Unknown. studio_ui::clause avoids doubled periods when reasons are embedded in longer sentences; graph_store::nothing_sent aligns log wording with the gateway.

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

@cursor

cursor Bot commented Oct 30, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 1c321d48-16d4-4b82-85ca-5acf824aeb32)

@ragnorc
ragnorc force-pushed the fix/write-not-sent-20261030 branch from aa80fb0 to 46a7e29 Compare October 31, 2026 02:15
@ragnorc
ragnorc changed the base branch from fix/sign-in-source-door-20261030 to main October 31, 2026 02:15
@cursor

cursor Bot commented Oct 31, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 66b2bae8-e38f-4407-8533-3b1a958eeb84)

…verywhere

A refused connection is described by its kind as well as judged by it, so Windows reads as other systems do; a fix dropped unsent and a merge whose head could not be read are not sent, not refused; a reason quoted into a sentence that goes on ends once (studio_ui::clause), the toast included; a never-sent write is logged with the gateway's words; a write and a Blob write share one send-and-judge step.
@ragnorc
ragnorc force-pushed the fix/write-not-sent-20261030 branch from 46a7e29 to cef06d0 Compare October 31, 2026 02:50
@cursor

cursor Bot commented Oct 31, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 61f23d51-97ba-4b8e-9531-5a9a57dd51ed)

@ragnorc
ragnorc merged commit b688b14 into main Oct 31, 2026
7 checks passed
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