Skip to content

docs: the failure shapes a client cannot learn from the routes - #676

Merged
Wirasm merged 1 commit into
mainfrom
docs/client-failure-shapes
Jul 28, 2026
Merged

docs: the failure shapes a client cannot learn from the routes#676
Wirasm merged 1 commit into
mainfrom
docs/client-failure-shapes

Conversation

@Wirasm

@Wirasm Wirasm commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Documentation only — no behaviour changes. Three distinctions the wire makes that the docs did not, all found by helm porting against the merged surface, and in two cases only after the wrong guess had shipped.

An empty changedFiles is not always "clean"

helm-migration.md tells clients to derive collidesWith from git.changedFiles and never mentioned git.error. There are three states, not two:

Shape Means
git absent not measured
git present, changedFiles: [], no error measured, genuinely clean
git present, changedFiles: [], error set measurement failed

worktree-status.ts captures git failures as data (error?: string, documented there as "any git failure captured here, NEVER thrown") and leaves the list empty when that fires. A client following the doc as written would report "collides with nothing" at exactly the moment it cannot tell — and that answer feeds a land gate.

The list itself is not optional; git is. A client guarding an optional changedFiles is guarding a state that never occurs while leaving the real one unguarded.

POST /land returns 409 from two places, with different bodies

refused merge        409  { ...full land report, dryRun: false }
unresolvable target  409  { error, code }

kildResultStatus maps every non-not_found failure to 409, so an archived or orphaned target answers with a bare {error, code} while a merge that would not apply answers with the entire report — which also contains an error. That field therefore cannot discriminate them; wouldMerge can.

A client decoding every 409 as a report throws on the bare one and replaces a message that names the fix with an opaque decode error:

{ "error": "kild c8971dd5 is archived (its agents are gone) — address its tree as 'sidebar-observe'",
  "code": "invalid_state" }

Losing that makes a correctly-behaving engine look broken. Documented rather than changed: the two really are different kinds of failure, and helm is mid-port against this surface.

The transcript route refuses for an attached agent

owned     200  { entries: [{ role, text, toolCalls? }], total }
attached  404  { error: "agent @claude has no pi session file (yet)" }

ownership decides the source, not the fidelity. Refusing is correct — an empty transcript would assert the agent had done nothing, when the truth is the question has no answer for a harness kild does not own. Those agents' history is the kild message log, a different source rather than a thinner view.

Also noted: an assistant turn that only calls tools has text: "" with the calls in toolCalls, so a renderer reading text alone silently drops precisely the turns that acted.

Verification

Every claim was checked against the source rather than transcribed from the report that raised it — server.ts:907-923 and kildResultStatus for the dual 409, server.ts:257-271 for the transcript refusal, worktree-status.ts for the error semantics.

Three things a client had to discover by hitting them. All three cost real
debugging time during helm's port, and in two cases the wrong guess had
already shipped before the payload proved it wrong.

An empty `changedFiles` is not always "clean". The list is never absent —
`git` is what is optional — so "not measured" is `git` missing, and the state
that catches people is the third one: `worktree-status.ts` captures git
failures as data (`error?: string`, "NEVER thrown") and leaves the list empty
when it fires. helm-migration.md told clients to derive collisions from that
list and never mentioned `error`, so a client following it would state
confidently that a kild collides with nothing at the exact moment it cannot
tell — feeding a land gate.

`POST /land` answers 409 from two places with different bodies: a refused
merge returns the whole report, and a target `resolveKild` refused returns a
bare `{error, code}`. Both carry `error`, so that field cannot discriminate
them; `wouldMerge` can. A client decoding every 409 as a report throws on the
bare one and replaces a message that names the fix ("address its tree as
'sidebar-observe'") with an opaque decode failure — which makes a
correctly-behaving engine look broken.

The transcript route refuses for an attached agent rather than returning an
empty transcript. `ownership` decides the source, not the fidelity: there is
no pi session behind a harness kild does not own, and empty would assert the
agent had done nothing when the truth is the question has no answer. Also
noted: an assistant turn that only calls tools has `text: ""`, so a renderer
reading `text` alone drops exactly the turns that acted.

No behaviour changes. Each of these is a real distinction the wire makes; the
documentation simply did not make it.
@Wirasm
Wirasm merged commit 7408074 into main Jul 28, 2026
3 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