Repository navigation
feat: say what a waiting session is waiting for - #20
Merged
Merged
Conversation
The `waiting` report now carries a `reason`, so the bell on a lich card says
why the session is blocked rather than only that it is. Client side of the
contract change that landed in lich; the field is optional there, trimmed and
capped, and belongs to `waiting` alone.
Each harness hands over something different to say it with, and none of them
was taken off documentation:
- Claude Code's `Notification` message is already a sentence written for a
human ("Claude needs your permission to use Bash"), and it writes one for
the plain idle-at-the-prompt nudge too — both go out unchanged, and lich
still decides which arrived.
- Codex's `PermissionRequest` has no message field at all, so the report is
the tool it is asking about with the same words a busy report puts on the
card behind it: `apply_patch: internal/terminal/usage.go`. That second half
moves into hooks/detail.jq, shared with report-tool.sh so the two readings
of one tool call cannot drift.
- opencode's four `.asked` events spell it four ways, so lich.js reads it by
field the way it already reads a tool call: `permission`, the v2 `action`,
then a question's own short `header` with its full text behind it. The
generic suffix rule stays — a prompt type nobody enumerated still reports
`waiting` with no reason, which is the documented degrade.
omp and Crush report no `waiting` at all, so neither gains anything here.
A reason it cannot build is never a reason to drop the report: an absent one,
a blank one and a payload with nothing to read all still ring the bell. Without
jq the message stays home, as the detail already did, but Codex's tool name is
an identifier and goes out alone.
The vendored session-start fixtures were a case behind lich, which has since registered `omp` as a provider id. Refreshed, so `fixtures-drift` stops failing on a copy nobody refetched. That retires the workaround it forced: omp's session-start assertion skipped the contract's own "unregistered provider" rule, because the rule fired on an otherwise correct payload. It was written to fold back into the shared assertion the day the case landed, and this is that day — so it does, and the imports it needed go with it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Client side of the session-state contract change in lich: the
waitingreport now carries areason, so the bell on a card says why the session is blocked rather than only that it is.reasonis optional, free text, trimmed and capped by lich at 120 runes, and rideswaitingalone — lich drops it on every other state. Nothing else in the contract moved;tool/detailare untouched.Where each harness's reason comes from
Measured against what is installed here, not read off documentation.
Notificationpayload'smessagetool_name, qualified with the samedetaila busy report carriespermission, the v2action, or a question'sheaderwaitingat allClaude Code is the only harness that hands over a sentence written for a human (
Claude needs your permission to use Bash), and it writes one for the plain idle-at-the-prompt nudge too — both go out unchanged, and lich still decides which arrived.Codex's
PermissionRequesthas no message field at all, so the report is the tool it is asking about with the words a busy report already puts on the card behind it:apply_patch: internal/terminal/usage.go,Bash: go test ./....opencode's four
.askedevents spell it four ways, solich.jsreads it by field the way it already reads a tool call. The generic suffix rule is untouched: a prompt type nobody enumerated still reportswaitingwith no reason, which is the documented degrade.One refactor
report-tool.sh's inline detail jq moves tohooks/detail.jq, now read by both scripts. Codex's reason is that same reading of a tool call, and one copy is what stops the two from drifting.Client rules, kept
waitingreads stdin. A hook blocked on a pipe nobody writes to would hold the turn it must never block.jqthe message stays home — arbitrary text a hand-built body cannot escape, the same rule the detail already followed. Codex's tool name is an identifier and goes out alone, so Windows keeps a reason.PermissionRequestan exit2would deny the request.Second commit:
ompis a registered provider nowSeparate from the feature, and reviewable on its own.
fixtures-driftwas already failing onmainfor an unrelated reason: lich has registeredompas a provider id and the vendoredsession-start.jsonlwas a case behind. Refreshed — which retires the workaround it had forced, where omp's session-start assertion skipped the contract's own "unregistered provider" rule. That skip was written to fold back into the shared assertion the day the case landed; this is that day. Drop the commit if you would rather have it as its own PR.Test plan
node --test tests/*.test.mjs— 169 passbusy/done/idleverified not to read stdinhook contract testsgreen in CIfixtures-driftfails on exactly the fourreasonlines, and nothing else — it diffstests/fixturesagainstlich@main, which has not merged the contract change yet. This merges after lich does, and goes green on its own then.Two things worth flagging
Two of these were caught by the new tests rather than by review: a whitespace
messagewent out as" ", and a non-string one put{on the card. Both are guarded now.hooks/detail.jqis the first non-script file the hooks read at run time, resolved viadirname "$0"— which is howwin-run.cmdpasses it too.