cc-chat: a conversation with an agent, from a page #71

Merged
mph merged 51 commits from cc-chat into main 2026-09-05 18:56:55 +00:00
Owner

Closes the cc-chat effort's SDK half: the last local slice, 06-gladys-global-chat, plus the
work slices 02–05 left on this branch.

The card (frozen)

Goal — I hold a working day's conversation with Gladys from the dashboard: ask her about work
in progress, act on what she answers, leave, and come back after a break knowing what she still
remembers — on <cc-chat>, with both hand-rolled chat generations deleted.

Fit test — "I ran a real working day's conversation with Gladys off the new page, including
coming back to it after a break." It closes only on Michal's read: nothing he knows about is
missing, nothing foreign is present, and does she still remember what we were talking about is
answered correctly.

Fit: passed. Michal, 2026-09-05, against the live system running this build.

What this branch ships

  • The transcript contract — SessionApi as Tapir descriptions a host mounts anywhere, with
    the addresses written down once in SessionEndpoint so the host and the browser cannot drift.
    Reading, rendering, following, artifacts, sending, interrupting, starting.
  • The cc-* vocabulary — semantic elements in light DOM, no styles of our own, and
    cc-chat.css beside the bundle as one example of dressing them. One producer, on the JVM.
  • The Claude Code adapter — a vendor log read as a transcript: what a person typed told apart
    from what the tool injected, tool calls kept as the fields they were called with, subagent
    sidechains dropped, a session's beginning marked where the page reached it.
  • Following — the stable point decided by comparing two renderings rather than by reasoning
    about which rows may move, so the rule holds however the rendering changes.
  • The live read — LiveEntries puts the running session's own stream into the shape the
    transcript will hold, so one reader renders both accounts and a row has no second way of being
    drawn. Measured on the running page: his own words 1192 ms → 3 ms, and a tool's output reaching
    the page 140 ms before the file holds it.
  • A turn that ends badly says so — TurnTrouble is the few endings a reader would act on
    differently; each is its own row and its own reason, said where the answer would have been.
  • Structured input — a query carries the words, the context the application attached, and the
    channel that delivered it, as blocks on stdin rather than concatenated prose.
  • The owned archive — query mirrors the transcript it leaves behind, on the id the run
    reported. Gladys' chat is query-driven, so before this nothing she said was archived anywhere
    but the vendor's tree, which prunes at thirty days.

Review

Eight reviewers over the 44-commit diff — style, testing, security, scala3, composition, zio,
architecture, api. 3 critical, 6 warnings, 12 suggestions. Every critical and every cheap
warning is fixed on the branch (9d04365); the rest are parked with reasons.

Report: project-management/efforts/cc-chat/slices/06-gladys-global-chat/review-slice-2026-09-05.md

The two that would have shipped:

  • QueryOptions.withInput silently discarded what it carried, in direct and effectful.
    It sets prompt too, so the query ran and dropped every context item and the channel with no
    signal. Both runners now refuse it.
  • keepingNewest was pinned by nothing — the fix for the scrolling fault Michal reported,
    documented at both call sites and asserted nowhere. Five jsdom tests now, proven fallible.

Also fixed: docs that no longer compiled against the reshaped UserMessage; TurnTrouble and the
structured-input types reaching none of the three module facades; a background task's result
naming the wrong session for the archive; and the whole prompt going to the log at INFO.

Follow-ups (parked, not blocking)

  • LogEntryPayload as an enum, and explicit cases at the five dispatch sites — one decision
    for it and sealed trait Message together, not a case-by-case drift.
  • Percent-encoding in SessionEndpoint.artifactUrlFor — nothing reachable, but the file is
    cross-built so it needs an encoder that is not java.net.URLEncoder.
  • The archive mirror's semaphore is minted per query call, so two concurrent queries resuming
    one session can interleave writes into one mirror file.
  • SessionApi.history and artifact were reported unroutable; diagnosed at close and both
    route
    — the probe asked for an address the contract does not define. Said at source in
    SessionEndpoint.path.
  • Rendering stutter while a turn runs; the likely place is a tail replacing more rows than it
    needs to, but that is a guess and the first step is to measure which rows move.

Lessons

18 entries triaged with Michal before this PR. Promoted: one procedures skill
(server-readiness — a readiness check is sound only if it cannot pass against the previous
build), two CLAUDE.md lines, seven fixed-at-source. Filed: procedures#697, procedures#698.

Next

Michal's sequence: this PR, then release 0.6.0 and move procedures off the local snapshot onto
it, then the procedures PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x

Closes the `cc-chat` effort's SDK half: the last local slice, **06-gladys-global-chat**, plus the work slices 02–05 left on this branch. ## The card (frozen) **Goal** — I hold a working day's conversation with Gladys from the dashboard: ask her about work in progress, act on what she answers, leave, and come back after a break knowing what she still remembers — on `<cc-chat>`, with both hand-rolled chat generations deleted. **Fit test** — "I ran a real working day's conversation with Gladys off the new page, including coming back to it after a break." It closes only on Michal's read: nothing he knows about is missing, nothing foreign is present, and *does she still remember what we were talking about* is answered correctly. **Fit: passed.** Michal, 2026-09-05, against the live system running this build. ## What this branch ships - **The transcript contract** — `SessionApi` as Tapir descriptions a host mounts anywhere, with the addresses written down once in `SessionEndpoint` so the host and the browser cannot drift. Reading, rendering, following, artifacts, sending, interrupting, starting. - **The `cc-*` vocabulary** — semantic elements in light DOM, no styles of our own, and `cc-chat.css` beside the bundle as one example of dressing them. One producer, on the JVM. - **The Claude Code adapter** — a vendor log read as a transcript: what a person typed told apart from what the tool injected, tool calls kept as the fields they were called with, subagent sidechains dropped, a session's beginning marked where the page reached it. - **Following** — the stable point decided by comparing two renderings rather than by reasoning about which rows may move, so the rule holds however the rendering changes. - **The live read** — `LiveEntries` puts the running session's own stream into the shape the transcript will hold, so one reader renders both accounts and a row has no second way of being drawn. Measured on the running page: his own words 1192 ms → 3 ms, and a tool's output reaching the page 140 ms *before* the file holds it. - **A turn that ends badly says so** — `TurnTrouble` is the few endings a reader would act on differently; each is its own row and its own reason, said where the answer would have been. - **Structured input** — a query carries the words, the context the application attached, and the channel that delivered it, as blocks on stdin rather than concatenated prose. - **The owned archive** — `query` mirrors the transcript it leaves behind, on the id the run reported. Gladys' chat is query-driven, so before this nothing she said was archived anywhere but the vendor's tree, which prunes at thirty days. ## Review Eight reviewers over the 44-commit diff — style, testing, security, scala3, composition, zio, architecture, api. **3 critical, 6 warnings, 12 suggestions.** Every critical and every cheap warning is fixed on the branch (`9d04365`); the rest are parked with reasons. Report: `project-management/efforts/cc-chat/slices/06-gladys-global-chat/review-slice-2026-09-05.md` The two that would have shipped: - **`QueryOptions.withInput` silently discarded what it carried**, in `direct` and `effectful`. It sets `prompt` too, so the query ran and dropped every context item and the channel with no signal. Both runners now refuse it. - **`keepingNewest` was pinned by nothing** — the fix for the scrolling fault Michal reported, documented at both call sites and asserted nowhere. Five jsdom tests now, proven fallible. Also fixed: docs that no longer compiled against the reshaped `UserMessage`; `TurnTrouble` and the structured-input types reaching none of the three module facades; a background task's result naming the wrong session for the archive; and the whole prompt going to the log at INFO. ## Follow-ups (parked, not blocking) - [ ] `LogEntryPayload` as an `enum`, and explicit cases at the five dispatch sites — one decision for it and `sealed trait Message` together, not a case-by-case drift. - [ ] Percent-encoding in `SessionEndpoint.artifactUrlFor` — nothing reachable, but the file is cross-built so it needs an encoder that is not `java.net.URLEncoder`. - [ ] The archive mirror's semaphore is minted per `query` call, so two concurrent queries resuming one session can interleave writes into one mirror file. - [ ] `SessionApi.history` and `artifact` were reported unroutable; diagnosed at close and **both route** — the probe asked for an address the contract does not define. Said at source in `SessionEndpoint.path`. - [ ] Rendering stutter while a turn runs; the likely place is a tail replacing more rows than it needs to, but that is a guess and the first step is to measure which rows move. ## Lessons 18 entries triaged with Michal before this PR. Promoted: one `procedures` skill (`server-readiness` — a readiness check is sound only if it cannot pass against the previous build), two `CLAUDE.md` lines, seven fixed-at-source. Filed: `procedures#697`, `procedures#698`. ## Next Michal's sequence: this PR, then release `0.6.0` and move `procedures` off the local snapshot onto it, then the `procedures` PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
mph added 48 commits 2026-09-05 18:06:58 +00:00
The close of 05 flushed three parked items and missed the largest: the
global chat is the effort's own success read — the intent asks for both
of procedures' chat generations gone — and it had no item at all.

Michal set its order: before 0.6.0 is cut. It is RPC-turn-shaped where
every consumer the contract has met is session-shaped, so it is the one
likeliest to find a hole, and finding it after publishing is worse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS9XLuXRvpbEuV9ZE3Whn1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS9XLuXRvpbEuV9ZE3Whn1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS9XLuXRvpbEuV9ZE3Whn1
`ClaudeCode.session` could mirror its transcript into an archive; `ClaudeCode.query`
could not. Gladys' dashboard conversation is driven turn by turn through `query`
with `--resume`, so every turn she has ever held lives only in the vendor tree,
which prunes at 30 days.

`query` now takes the same optional `ArchiveConfig` the session takes, and
`QueryCustody` mirrors the run's transcript once its message stream is finished
with — on the id the result reported, falling back to the resumed id when the run
died before reporting one, so a resume-fallback's newly minted session is archived
rather than the one the CLI declined.

The archive E2E suite proving this could not have caught it: it gates on
`ANTHROPIC_API_KEY`, and it isolates `CLAUDE_CONFIG_DIR` to get a clean transcript
tree — which also isolates the login. On a subscription machine the guard skipped
it, and lifting the guard alone made it fail with "Not logged in". `ClaudeZioSpec`
now owns both halves: `onlyIfClaude` (CLI on the PATH plus a key *or* a login) and
`isolatedConfigDir`, which carries the credentials into the fresh tree. Both
archive E2E tests and the ClaudeCode E2E test now run against the real CLI here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A session is one transcript, and an agent resumed into a new one starts from its
standing context rather than from what was said before. Gladys' session expires
after two hours idle, so the id rotates and the page a reader comes back to is a
different conversation from the one they left — with nothing on it saying so.

`Transcript` now carries `reachesStart`, and `TranscriptMarkup` opens with a
`cc-session-start` row when it is set. The fact travels from the read that knows
it: `TranscriptHistory.of(sessionId, page)` sets it from `page.older.isEmpty`,
because a page with nothing older behind it reached the transcript's beginning.
The follower overload holds what it heard since it subscribed, not a page it
read, so it claims nothing — a boundary drawn where none was seen would answer
the reader's question wrongly.

What the row means is the host's to write, as with every other row: the element
ships the fact and the moment, and `cc-chat.css` shows a project how to say it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
`UserInput` kept the person's words apart from what the application attached, but
only a session could send it: structured input reaches the CLI over stdin, and
`query` passed its prompt as an argument. Anything driven turn by turn — Gladys'
whole conversation — had no way to attach context except by folding it into the
message text, which the transcript then reports as words she was typed at.

`QueryOptions.withInput` takes a `UserInput` and sets `prompt` to its own text, so
the two can never disagree about what was said. A query holding one runs the CLI
with `--input-format stream-json`, drops the trailing prompt argument, and writes
the same `SessionStdin.userInputLine` a session writes, then closes stdin — which
is what ends a run reading until end of input.

Proven against the real CLI, in the archive E2E where the transcript can be read
back: the words are the final block alone, what was attached rode in a block of
its own, and it never leaked into the words.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A standing conversation outlives the CLI sessions that carry it: one expires
after long enough idle, and the next thing said starts another. Between them
there is nothing to read — and a host had only `SessionNotFound` to say so, which
answers "does the agent still remember" with an error rather than with the
answer, which is no.

`ConversationDormant` carries the conversation's own name and says what is true:
nothing said before is still held, and the next thing said starts a new session.
Same 404 — there is no transcript at that address — with a reason code of its own
so a host can style and word it as the ordinary thing it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
The vendor sends every tool answer as a `user` message whose content sits under
`message.content`, as blocks — the same place and shape an assistant reply uses.
`parseUserMessage` read a top-level `content` string instead, so no real user
message ever parsed: each one fell through to `UnknownMessage` and every tool
answer was dropped from the live stream.

Measured off `claude --print --verbose --output-format stream-json`, not assumed:
the message carries `uuid`, `parent_tool_use_id` and a `tool_result` block. The
fixture that stood here was invented to match the parser, so it passed for the
whole life of the defect.

`UserMessage` now carries blocks, its vendor uuid and its parent tool-use id, as
`AssistantMessage` does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
`LiveEntries` puts the live stream in the shape the transcript will hold, so the
same reader renders both and there is no second way for a row to be drawn. A
test pins the guarantee that makes this safe: for one turn — words, a call, an
answer that failed, a turn this SDK composed — the live rows and the rows read
back off the file are equal.

`TranscriptFollow` holds what it has heard beside what it has read, and drops a
turn from the heard side as the file catches up. The two accounts are matched by
the vendor's own uuid, which the stream and the file carry alike (measured, not
assumed). A turn with no uuid is one only this host has an account of — the CLI
never echoes back the prompt it was given — so it is matched on what it says.

The archive's follow now says whether the page it opened with reached the
session's beginning, and `rowsOf` draws the `cc-session-start` row, so a
following page can show where a session began. It could not before: the row was
built into `of` alone, which only the reading path calls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A host decides how it shows the standing line, and the dashboard hides it while
the agent is ready and shows it while the agent works — which costs the
conversation 47px the moment a turn starts. The rows lost that height without
anyone reading where the reader was, so a reader watching the newest row was
silently moved off it, and every row after that landed below the fold: the page
now read as one they had scrolled away from, so nothing scrolled again.

Measured on the running page: at 37 ms the turn's own row scrolled correctly, at
86 ms the line appeared and the transcript's shown height dropped 301 -> 254, and
her answer at 2352 ms arrived out of sight.

Both places where this element changes how much of the conversation is shown —
the standing line and the composer arriving — now keep the reader at the newest
row, the same way rows arriving already did. jsdom computes no layout, so the
arithmetic is what is unit-tested and the behaviour is verified in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A turn that dies stops: the transcript ends mid-conversation, the standing
line returns to ready, and the page says nothing about it. A reader is left
waiting for an answer that is not coming, and a spent turn budget, a refused
permission, an API that did not answer and a process that died all read alike
— as silence.

The end-of-turn envelope is the only account of the ending anyone gets: the
vendor writes down the conversation, not what ended it, and a process that
died wrote nothing at all. So `LiveEntries` reads it as one entry rather than
dropping it, `TurnTrouble` is the few kinds a reader would act on differently,
and the entry goes through the same reader every other row goes through —
`cc-trouble[reason]`, the tag a whole page becomes when there is nothing to
show, said here in the place the answer would have been.

A turn the reader stopped is not trouble; neither is a background task
reporting back. What the ending said for itself rides in the vendor's own
tokens, because that is the part a reader takes to whoever can act on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
The row is open, so the preview in its heading and the body under it printed
the same short line twice — seen on the page: "error_max_turnserror_max_turns".
A preview stands in for a body a reader has not opened, and this row has none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A label is a tool's name everywhere else — "Bash", "Read" — so it never
wraps. On a stopped turn it is a sentence, and on the panel where this chat
lives it ran 2px past its own row and would have run much further in a
narrower one. Measured on the harness page: 333px of label in a 331px
heading, and 296 in 331 once it wraps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
A probe of the live system reported that `history` and `artifact` "do not
route, in either mounting", and it was parked as a blocker on the release.
Both route. Reading a transcript is the session's own address and carries no
segment after it, so the probe's `…/gladys/history` reached the dashboard's
catch-all — and on a host that also serves pages, an address the contract
does not define answers exactly like a mounting that is broken.

`SessionEndpoint.path` now says that, and says what an undefined segment
reaches, because that is where a reader looks for an address.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
**A method that dropped what it was given.** `QueryOptions.withInput` is
carried only by the ZIO runner; `direct` and `effectful` hand the CLI a prompt
argument and write no stdin. Because `withInput` also sets `prompt`, a query
there ran — and discarded every context item and the channel that carried it,
with nothing in the answer to say so. That is the same failure this effort
already caught once on the other side of the wire. Both now refuse the query.
`input` also moved to the end of the parameter list, where an optional
addition belongs in a published case class.

**Documentation that no longer compiles.** `UserMessage` carries blocks now;
`README.md` still pattern-matched the old one-field shape and
`ARCHITECTURE.md` still documented `content: String`. The first example a
consumer copies has to work.

**A vocabulary word no consumer could name.** `TurnTrouble` and
`TurnTroubleLogEntry` reached none of the three facades, though every other
payload case does and each facade's test enumerates them; `UserInput`,
`ContextItem` and `Channel` reached only `zio`, so a `direct` consumer could
not spell the argument to a method their `QueryOptions` advertises.

**A background task could have named the wrong session.** `QueryCustody` took
any result as this run's; a task notification carries an origin and a session
of its own, and mirroring on it would archive another conversation's
transcript under this one's name. Guarded, as `SessionProcess` already was.

**A prompt is what a person said.** It no longer goes to the log at INFO; its
length says the query started without saying any of it.

**Two mechanisms nothing pinned.** `keepingNewest` — the fix for the scrolling
Michal reported — is now tested at both branches and at its call site, with
jsdom given the three numbers a browser would have computed. The structured
stdin the CLI reads is tested without spawning one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
Promoted:
- three readiness-check entries → one `procedures` skill, server-readiness. The rule was learned
  on the morning of 2026-09-03 and broken again the same afternoon by the same check, which is
  the argument for a skill rather than a note.
- a module's own test run does not compile the integration sources → `procedures` CLAUDE.md
- seven entries → fixed-at-source, each citing the commit that corrected the artefact
- a coverage file that OOMs the daemon → iterative-works/procedures#697
- a restart that exits 0 on a failed frontend build → iterative-works/procedures#698
- the background channel that discards the preparer's work → the tracker's PARKED list; it is
  work, not knowledge

Rejected:
- `--no-daemon` as a documented escape hatch: Michal's call — the daemon should be fixed, not
  worked around, and documenting the way around it would make the fault permanent.

Retained:
- `pkill -f` names the build and not the instance — the guard already lives in a versioned hook
  and in `scripts/live-system`.

Corrected at triage: the entry about an expired conversation said its source fix was "not yet".
It had been fixed in this slice — `TranscriptFailure.ConversationDormant` — and the entry was
stale by the time anyone read it.

Still pending: the wire-format fixture entry, awaiting Michal's read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
workflow: triage lessons for cc-chat (18 of 18)
Some checks failed
CI / Check Formatting (pull_request) Successful in 1m0s
CI / Compile (pull_request) Successful in 1m17s
CI / Check Linting (pull_request) Successful in 59s
CI / Run Tests (pull_request) Successful in 1m29s
CI / Check Bundle Size (pull_request) Successful in 55s
CI / Run Integration Tests (pull_request) Failing after 1m48s
cd308da83a
Promoted: a fixture standing in for the vendor's wire format is captured from
the CLI, never written from what the parser expects — the two agree with each
other and the suite passes for as long as both are wrong. The capture command
is in the rule, so the next reader re-takes the bytes instead of trusting
ours.

One wording change to a decided entry: it described the morning's lesson as
having "stayed a `_pending_` observation". The literal marker in prose keeps
the close gate's own count above zero for ever; "untriaged" says the same.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
fix(core): a user turn arrives in two shapes, and both are real
Some checks failed
CI / Compile (pull_request) Successful in 1m12s
CI / Run Tests (pull_request) Successful in 1m44s
CI / Check Bundle Size (pull_request) Successful in 1m14s
CI / Run Integration Tests (pull_request) Failing after 2m12s
CI / Check Formatting (pull_request) Successful in 42s
CI / Check Linting (pull_request) Successful in 1m18s
1788111ea8
CI caught this on the pull request, in the one suite that runs a CLI —
`effectful.itest` reported "Missing message type: UserMessage. Found types: …
UnknownMessage …".

The parser was taught this week that `message.content` is an array of blocks,
because that is how every tool answer arrives and the old fixtures had
invented a string. It is both. Measured over four sessions of one agent: 134
entries carry blocks, 50 carry a bare string. Whichever shape the parser knows
alone, it drops the other as an unknown message — losing either every tool
answer or every typed turn.

Both are read now, and both are pinned. The lesson promoted an hour ago said a
wire-format fixture is captured from the vendor rather than written from what
the code expects; the mock script that failed here is the vendor's other
shape, and it was right to keep it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
fix: the invented user shape the parser fix uncovered, in two places
All checks were successful
CI / Check Formatting (pull_request) Successful in 39s
CI / Compile (pull_request) Successful in 1m2s
CI / Check Linting (pull_request) Successful in 56s
CI / Run Tests (pull_request) Successful in 1m28s
CI / Check Bundle Size (pull_request) Successful in 46s
CI / Run Integration Tests (pull_request) Successful in 1m42s
84101fcdcc
The mock CLI script `effectful` runs against still emitted
`{"type":"user","content":"…"}` — content at the top level, no `message`
wrapper, a shape nothing sends. `direct`'s mocks were replaced with the
measured shape this week; this resource was missed, and it is what CI was
failing on.

Behind it, a second leftover from reshaping `UserMessage`: the test asserted
`content.contains("Hello Claude")` on what is now a list of blocks. `contains`
takes `Any`, so it compiled and could never be true. It reads the words out of
the blocks now.

`__.itest` passes locally, against the real CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
workflow(sl): close cc-chat/06-gladys-global-chat (flush parked items)
All checks were successful
CI / Check Formatting (pull_request) Successful in 46s
CI / Compile (pull_request) Successful in 1m2s
CI / Check Linting (pull_request) Successful in 57s
CI / Run Integration Tests (pull_request) Successful in 1m38s
CI / Run Tests (pull_request) Successful in 1m27s
CI / Check Bundle Size (pull_request) Successful in 46s
31954aafa4
Fit passed on Michal's read, 2026-09-05. The effort's success read is met:
both hand-rolled chat generations are gone and Gladys' own conversation runs
on `<cc-chat>`.

Seven items flushed from the tracker's PARKED into the effort map, each with
the reasoning it was parked under.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTrxcxBQiNjXh79dN8Sh5x
mph merged commit 1ea8930dac into main 2026-09-05 18:56:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/claude-code-query!71
No description provided.