instar 1.3.984 → 1.3.986
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +13 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +17 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +48 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +15 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +26 -15
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/claudeReadinessProbe.d.ts +137 -0
- package/dist/core/claudeReadinessProbe.d.ts.map +1 -0
- package/dist/core/claudeReadinessProbe.js +181 -0
- package/dist/core/claudeReadinessProbe.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +21 -21
- package/src/templates/scripts/telegram-reply.sh +61 -0
- package/upgrades/1.3.985.md +109 -0
- package/upgrades/1.3.986.md +153 -0
- package/upgrades/side-effects/booting-pane-read-as-ready.md +222 -0
- package/upgrades/side-effects/misplaced-flag-sent-as-message-text.md +193 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
**A flag placed after the topic id was sent to the user as literal message text, and the
|
|
9
|
+
setting it carried was silently dropped.**
|
|
10
|
+
|
|
11
|
+
`telegram-reply.sh` parses flags in a loop that breaks at the first non-flag argument — the
|
|
12
|
+
topic id. Everything after that becomes the message via `MSG="$*"`. So
|
|
13
|
+
`telegram-reply.sh 29723 --tone-ack B15 --tone-reason "why"` sent the user the literal text
|
|
14
|
+
`--tone-ack B15 --tone-reason why`, while the tone-advisory override never reached the
|
|
15
|
+
server. Both failures were silent; the script exited 0.
|
|
16
|
+
|
|
17
|
+
This is measured, not inferred. The pre-fix template is captured verbatim as a test fixture
|
|
18
|
+
(at the same SHA now registered in the migrator's shipped-SHA allowlist) and run against a
|
|
19
|
+
stub of the reply route: exit 0, received text containing `--tone-ack`, and no
|
|
20
|
+
`toneAdvisoryAck` in the metadata.
|
|
21
|
+
|
|
22
|
+
**The cost was a corrupted measurement.** Because the override never applied, the tone gate
|
|
23
|
+
re-reviewed the send as an ordinary message whose text now began with option noise. The
|
|
24
|
+
verdict looked absurd, was read as a malfunction, and a **correct** check was graded `wrong`
|
|
25
|
+
in the decision-quality data — the data used to decide which checks to trust. That record is
|
|
26
|
+
durable and is not retracted by this change.
|
|
27
|
+
|
|
28
|
+
**The root cause was not the script.** These flags were documented nowhere agent-facing:
|
|
29
|
+
not in the script's own usage header (which covers `--format` and `--stdin-base64`), and not
|
|
30
|
+
in the agent instructions, which document the HTTP `metadata.*` fields while simultaneously
|
|
31
|
+
mandating "ALWAYS the relay script, never a hand-rolled curl". The bridge between the two
|
|
32
|
+
did not exist, so the invocation had to be guessed — and the script accepted the guess
|
|
33
|
+
without complaint. A capability shipped without instructions, plus a tool that stayed silent
|
|
34
|
+
on the only usage an uninstructed caller would try.
|
|
35
|
+
|
|
36
|
+
An inconsistency underneath made the fix obvious once seen: the script **already** treated an
|
|
37
|
+
unrecognised flag-shaped token as fatal when it appeared *before* the topic id. It was strict
|
|
38
|
+
about nonsense in one position and fully permissive about a real flag in the other. Both
|
|
39
|
+
positions are now treated the same.
|
|
40
|
+
|
|
41
|
+
Three parts, because the chain had three links:
|
|
42
|
+
|
|
43
|
+
1. **The script refuses** a `--*` argument after the topic id, printing the correct ordering
|
|
44
|
+
and the corrected command. Nothing is sent.
|
|
45
|
+
2. **A typo'd flag is caught too** (`--tone-akc`) — the realistic mistake, and equally silent
|
|
46
|
+
before. The guard matches the *shape*, not a list of known names, which is what makes it
|
|
47
|
+
useful rather than decorative.
|
|
48
|
+
3. **The flags are documented** — in the script's usage header and in the agent instructions,
|
|
49
|
+
with a worked example showing flags before the topic id.
|
|
50
|
+
|
|
51
|
+
**What this does not do:** it does not retract the false grade from 2026-07-26. Whether a
|
|
52
|
+
mistaken grade can be corrected at all — and whether correcting one erases the evidence that
|
|
53
|
+
it was ever made, which would be the opposite defect — is a separate open question, and is
|
|
54
|
+
deliberately not answered here. It also fixes only this script; `slack-reply.sh` and
|
|
55
|
+
`whatsapp-reply.sh` share the parse-then-break shape but carry no tone flags today, so they
|
|
56
|
+
are named rather than swept in.
|
|
57
|
+
|
|
58
|
+
## What to Tell Your User
|
|
59
|
+
|
|
60
|
+
If a stray double-dash option ever appeared in the middle of one of my messages to you, that
|
|
61
|
+
was this: a setting that belonged in the plumbing ended up in the text, and whatever it was
|
|
62
|
+
supposed to do quietly didn't happen. It can't reach you as text any more — the send is
|
|
63
|
+
refused and corrected instead.
|
|
64
|
+
|
|
65
|
+
The part worth knowing: one of those settings is how I record that I disagree with one of my
|
|
66
|
+
own safety checks. When it silently failed to apply, I misread the result and marked a check
|
|
67
|
+
as faulty when it had actually been right. So this wasn't only cosmetic — it put a wrong
|
|
68
|
+
entry in the records used to judge whether those checks are any good. That entry is still
|
|
69
|
+
there; this stops more from being created.
|
|
70
|
+
|
|
71
|
+
## Summary of New Capabilities
|
|
72
|
+
|
|
73
|
+
- A flag placed after the topic id is refused with an actionable message naming the correct
|
|
74
|
+
ordering, instead of being sent to the user as literal text with its effect dropped.
|
|
75
|
+
- A misspelled flag after the topic id is caught by the same guard.
|
|
76
|
+
- The tone-advisory reaction flags (`--tone-ack`, `--tone-reason`, `--tone-complied`,
|
|
77
|
+
`--tone-decision-ref`) are documented in the script's usage header and in the agent
|
|
78
|
+
instructions, with a worked example.
|
|
79
|
+
- Existing agents receive both the new script and the new documentation — the doc block is
|
|
80
|
+
sniffed on its own marker so an agent that already carries the surrounding section is not
|
|
81
|
+
skipped.
|
|
82
|
+
|
|
83
|
+
## Evidence
|
|
84
|
+
|
|
85
|
+
**Reproduction (before),** the pre-fix template against a stub reply route:
|
|
86
|
+
|
|
87
|
+
| invocation | exit | text received by the user | override applied |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `… 4242 --tone-ack B15 --tone-reason because` | `0` | `--tone-ack B15 --tone-reason because` | no |
|
|
90
|
+
|
|
91
|
+
**Observed after,** same stub:
|
|
92
|
+
|
|
93
|
+
| invocation | before | after |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| flag after the topic id | sent as message text, exit 0 | refused, exit 1, **zero requests sent** |
|
|
96
|
+
| typo'd flag after the topic id | sent as message text, exit 0 | refused, exit 1, zero requests |
|
|
97
|
+
| flags before the topic id | worked | works — text clean, `toneAdvisoryAck` + reason present |
|
|
98
|
+
| message on stdin containing a flag-shaped token | sent verbatim | sent verbatim (stdin is not inspected) |
|
|
99
|
+
| unknown flag before the topic id | `Unknown flag`, exit 1 | unchanged |
|
|
100
|
+
|
|
101
|
+
**Tests:** `tests/integration/telegram-reply-misplaced-flag.test.ts` (6) runs the real
|
|
102
|
+
template — and the pre-fix fixture — against a stub route, so "was it sent as text?" is
|
|
103
|
+
answered by the received payload rather than by reading the script.
|
|
104
|
+
`tests/unit/PostUpdateMigrator-toneAdvisoryFlagPosition.test.ts` (3) covers migration parity,
|
|
105
|
+
including the load-bearing case: an agent that already carries the tone-advisory section must
|
|
106
|
+
still receive the invocation guidance. That test was verified to FAIL when the second
|
|
107
|
+
migration block is disabled, so it cannot pass whether or not the migration exists. The full
|
|
108
|
+
telegram-reply and template-SHA surface (10 files, 51 tests) passes unchanged;
|
|
109
|
+
`npx tsc --noEmit` clean.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
**A session's startup banner — and a session's startup QUESTION — were read as an input prompt, so
|
|
9
|
+
a message could be typed into a pane that could not receive it, and the delivery was logged as a
|
|
10
|
+
success.**
|
|
11
|
+
|
|
12
|
+
`SessionManager.detectClaudePrompt` decided a pane was accepting input if the last six
|
|
13
|
+
non-blank lines contained `❯`, `›`, `bypass permissions`, or a mention of `/effort`,
|
|
14
|
+
`/model` or `/fast`. That last clause matched a bare slash-command mention **anywhere, in
|
|
15
|
+
any context, including prose**.
|
|
16
|
+
|
|
17
|
+
Claude Code's startup banner advertises slash commands in prose. The pane that caused the
|
|
18
|
+
incident carried, verbatim:
|
|
19
|
+
|
|
20
|
+
> `…Fable 5 draws down usage faster than Opus 4.8. Run /model and`
|
|
21
|
+
> `select Fable to use it. Learn more: https://support.claude.com/…`
|
|
22
|
+
|
|
23
|
+
so the probe matched a promotional line and declared a still-painting pane ready.
|
|
24
|
+
|
|
25
|
+
**Observed (topic 29723, 2026-07-26).** Inbound message at `17:44:49Z`, session respawned,
|
|
26
|
+
pointer prompt injected at `17:45:00Z` — but that session's own start hook did not complete
|
|
27
|
+
until `17:45:15Z`. The paste landed fifteen seconds early, was swallowed by the TUI redraw,
|
|
28
|
+
and the injector wrote `Injected initial message into "…" (915 chars, after stabilization
|
|
29
|
+
delay)`. A success line for a delivery that did not happen. The message reached the agent
|
|
30
|
+
only because the start hook's unanswered-message backstop re-reads recent history — an
|
|
31
|
+
unrelated path that happens to cover this hole and cannot be relied on to.
|
|
32
|
+
|
|
33
|
+
**The slash-command clauses are deleted, not tuned.** A first attempt kept them and required
|
|
34
|
+
"status-bar shape" (line-start or directly after an interpunct). Second-pass review falsified that:
|
|
35
|
+
the banner uses the same shape — `· /memory to free up context` and `+1 more · /status` are BANNER
|
|
36
|
+
lines — so the discrimination was still vocabulary, and `· /model to opt in` matched outright. The
|
|
37
|
+
line-start branch also reinstated the very dependency it claimed to remove: at a fixed pane width, a
|
|
38
|
+
copy edit shifting the wrap by ~14 characters puts the command at column 0 and the defect returns. A
|
|
39
|
+
signal that cannot separate an ADVERT for a command from a STATUS BAR showing one carries no
|
|
40
|
+
readiness information. The structural markers were widened instead — `? for shortcuts`,
|
|
41
|
+
`shift+tab to cycle` and `auto-accept edits` join `bypass permissions`, so a session in auto-accept
|
|
42
|
+
mode no longer depends on the prompt glyph being visible.
|
|
43
|
+
|
|
44
|
+
**Second defect, found by the operator: a startup question read as ready.** Claude Code paints the
|
|
45
|
+
same `❯` on a menu's focused option that it uses for the input box. This is strictly worse than the
|
|
46
|
+
banner — text typed at a banner is lost, but Enter at a menu SELECTS AN OPTION, so an arriving
|
|
47
|
+
message can answer a permission question on the operator's behalf. A focused menu is now its own
|
|
48
|
+
state, never ready.
|
|
49
|
+
|
|
50
|
+
**The verdict is no longer a boolean.** Three consumers ask this question and one of them KILLS a
|
|
51
|
+
live session when the answer is not-ready. "Still painting" and "waiting on an answer" want opposite
|
|
52
|
+
responses from it, so the probe now names which state it saw and each caller applies its own policy;
|
|
53
|
+
the destructive caller leaves a menu alone, waits a bounded moment for the always-on auto-resolver to
|
|
54
|
+
clear it, and only then treats the session as stuck.
|
|
55
|
+
|
|
56
|
+
Classification moved into a small pure module so the incident's literal pane text is a
|
|
57
|
+
regression fixture rather than a paraphrase.
|
|
58
|
+
|
|
59
|
+
## What to Tell Your User
|
|
60
|
+
|
|
61
|
+
If you ever messaged me, watched a session start, and then saw it sit there doing nothing with
|
|
62
|
+
your message apparently lost — this was one way that happened. I was deciding I was awake by
|
|
63
|
+
looking for certain command names on screen, and my own startup banner mentions those command
|
|
64
|
+
names while advertising a feature. So I read "still starting up" as "ready", typed your message
|
|
65
|
+
into a screen that was still drawing itself, and recorded it as delivered.
|
|
66
|
+
|
|
67
|
+
There is a second case, sharper than the first. Sessions sometimes start by asking a question,
|
|
68
|
+
and the marker drawn beside the selected answer is the same one drawn for the input box. So a
|
|
69
|
+
session waiting on a question also read as ready. At a banner, a message typed too early is
|
|
70
|
+
simply lost. At a question it is not lost — pressing Enter picks an answer, so an arriving
|
|
71
|
+
message could have answered a permission question on your behalf. That can no longer happen.
|
|
72
|
+
|
|
73
|
+
I now wait for the actual input box, or for a footer that only exists once the app is running,
|
|
74
|
+
and I treat a question on screen as somewhere not to type.
|
|
75
|
+
|
|
76
|
+
One honest limit: this makes typing too early far less likely, but I still report a message as
|
|
77
|
+
delivered on the basis of having typed it, not on the basis of it appearing. Confirming that
|
|
78
|
+
typed text actually arrived is a separate change, recorded and not bundled here.
|
|
79
|
+
|
|
80
|
+
## Summary of New Capabilities
|
|
81
|
+
|
|
82
|
+
- A pane showing only a startup banner is no longer classified as ready for input, so a message
|
|
83
|
+
is not injected before the session can receive it.
|
|
84
|
+
- A focused selection menu is recognised as its own state and is never ready, so an arriving
|
|
85
|
+
message cannot select an option in a question meant for the operator.
|
|
86
|
+
- Slash-command mentions no longer count as a readiness signal in any form — the discrimination
|
|
87
|
+
moved to markers that only exist once the TUI is running.
|
|
88
|
+
- All three permission-mode footers are recognised (`bypass permissions`, `? for shortcuts`,
|
|
89
|
+
`auto-accept edits`, `shift+tab to cycle`), where previously only one was — so a session in
|
|
90
|
+
auto-accept mode no longer depends on the prompt glyph being visible.
|
|
91
|
+
- The readiness verdict names which state it saw rather than answering yes/no, so the caller that
|
|
92
|
+
kills a stuck session no longer kills one that is merely waiting on a question.
|
|
93
|
+
- The readiness decision is a pure, separately-testable function, so the pane text from a real
|
|
94
|
+
incident is carried as a regression fixture instead of being described.
|
|
95
|
+
|
|
96
|
+
## Evidence
|
|
97
|
+
|
|
98
|
+
**Reproduction (before),** real pane text against the pre-fix clause:
|
|
99
|
+
|
|
100
|
+
| input | pre-fix verdict | correct verdict |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| the 2026-07-26 boot banner (contains `Run /model and`) | `ready` | not ready |
|
|
103
|
+
| `Do you want to proceed?` + `❯ 1. Yes` / `2. No` | `ready` | menu (never ready) |
|
|
104
|
+
| `Do you trust the files in this folder?` + two options | `ready` | menu (never ready) |
|
|
105
|
+
| `Run /fast to enable faster output on this plan.` | `ready` | not ready |
|
|
106
|
+
|
|
107
|
+
**Also falsified — the intermediate "status-bar shape" attempt,** kept here because it is the
|
|
108
|
+
reason the clause was deleted rather than narrowed:
|
|
109
|
+
|
|
110
|
+
| input | "shape" verdict | correct verdict |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `⚠ Fable 5 promotional access ends soon · /model to opt in` | `ready` | not ready |
|
|
113
|
+
| ` · /model to switch between Opus and Fable` | `ready` | not ready |
|
|
114
|
+
| soft-wrapped banner putting `/model` at column 0 | `ready` | not ready |
|
|
115
|
+
| `opus 5 • medium • /effort` (bullet separator) | not ready | ready |
|
|
116
|
+
|
|
117
|
+
**Observed after:**
|
|
118
|
+
|
|
119
|
+
| input | before | after |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| the 2026-07-26 boot banner | ready | **not ready** |
|
|
122
|
+
| any prose or interpunct mention of `/model`, `/fast`, `/effort` | ready | **not ready** |
|
|
123
|
+
| a focused startup question (3 real shapes) | ready | **menu — never typed into** |
|
|
124
|
+
| ordinary output listing `1.` / `2.` above the input box | ready | ready (not mistaken for a menu) |
|
|
125
|
+
| a single numbered line beside the prompt | ready | ready |
|
|
126
|
+
| drawn input box (`❯`) / codex prompt (`›`) | ready | ready |
|
|
127
|
+
| `⏵⏵ bypass permissions on` | ready | ready |
|
|
128
|
+
| `? for shortcuts` / `auto-accept edits` / `shift+tab to cycle` | **not ready** | **ready** (newly recognised) |
|
|
129
|
+
| banner that has since grown an input box | ready | ready |
|
|
130
|
+
| empty / whitespace pane | not ready | not ready |
|
|
131
|
+
|
|
132
|
+
**Both guards refuse.** Restoring the old slash-command clause fails six assertions; removing the
|
|
133
|
+
menu classification fails three. Both include the discrimination guard, whose only job is to prove
|
|
134
|
+
the probe still tells its three verdicts apart:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
# restoring the slash-command clause
|
|
138
|
+
× REGRESSION: the 2026-07-26 startup banner does NOT read as ready
|
|
139
|
+
× REGRESSION: a banner line in interpunct "status-bar shape" does NOT read as ready
|
|
140
|
+
× REGRESSION: a soft-wrapped banner putting /model at line start does NOT read as ready
|
|
141
|
+
× an agent echoing a slash command in its own output does NOT read as ready
|
|
142
|
+
× the probe discriminates > produces all three verdicts and is not stuck on one
|
|
143
|
+
Tests 6 failed | 12 passed (18)
|
|
144
|
+
|
|
145
|
+
# removing the menu classification
|
|
146
|
+
× REGRESSION: a startup question does NOT read as ready
|
|
147
|
+
× a menu is not ready to be typed into
|
|
148
|
+
× the probe discriminates > produces all three verdicts and is not stuck on one
|
|
149
|
+
Tests 3 failed | 15 passed (18)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Scope run:** `npx tsc --noEmit` clean; every tree-scanning test under `tests/unit` plus every
|
|
153
|
+
test that mocks `capture-pane` (the fixtures that feed this probe) — counts in the PR body.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Side-Effects Review — a startup banner, and a startup menu, were read as an input prompt
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `booting-pane-read-as-ready`
|
|
4
|
+
**Date:** `2026-07-26`
|
|
5
|
+
**Author:** `Echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent — VERDICT: CONCERN. Design changed in response; see Phase 5.`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`SessionManager.detectClaudePrompt` decided a pane was accepting input if the last
|
|
11
|
+
six non-blank lines contained `❯`, `›`, `bypass permissions`, `/(effort|model|fast)`,
|
|
12
|
+
or `(low|medium|high) · /effort`. Two independent defects:
|
|
13
|
+
|
|
14
|
+
**1 — the banner.** The slash-command clause matched a bare mention **anywhere, in
|
|
15
|
+
any context, including prose**. Claude Code's startup banner advertises slash
|
|
16
|
+
commands in prose; the incident pane carried:
|
|
17
|
+
|
|
18
|
+
> `…Fable 5 draws down usage faster than Opus 4.8. Run /model and`
|
|
19
|
+
> `select Fable to use it. Learn more: https://support.claude.com/…`
|
|
20
|
+
|
|
21
|
+
**2 — the menu (found by the operator).** Claude Code paints the **same `❯`** on a
|
|
22
|
+
menu's focused option that it uses for the input box. A session sitting on a
|
|
23
|
+
startup question therefore read as READY. This is strictly worse: text typed at a
|
|
24
|
+
banner is lost, but **Enter at a menu SELECTS AN OPTION**, so an arriving message
|
|
25
|
+
can answer a permission question on the operator's behalf.
|
|
26
|
+
|
|
27
|
+
**Observed (topic 29723, 2026-07-26).** Inbound `17:44:49Z` → respawn → pointer
|
|
28
|
+
injected `17:45:00Z` → that session's start hook completed `17:45:15Z`. The paste
|
|
29
|
+
landed 15s early, was swallowed by the redraw, and the injector logged
|
|
30
|
+
`Injected initial message into "…" (915 chars, after stabilization delay)` — a
|
|
31
|
+
success line for a delivery that did not happen. The message reached the agent only
|
|
32
|
+
via the start hook's unanswered-message backstop, an unrelated path.
|
|
33
|
+
|
|
34
|
+
**The slash-command clauses are DELETED, not tuned.** The first attempt kept them
|
|
35
|
+
and required "status-bar shape" (line-start or post-interpunct). Second-pass review
|
|
36
|
+
falsified that, and I verified each claim before accepting it:
|
|
37
|
+
|
|
38
|
+
| falsifying input | verdict under the "shape" fix |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `⚠ Fable 5 promotional access ends soon · /model to opt in` | `ready` (false positive) |
|
|
41
|
+
| ` · /model to switch between Opus and Fable` | `ready` (false positive) |
|
|
42
|
+
| soft-wrapped banner putting `/model` at column 0 | `ready` (false positive) |
|
|
43
|
+
| `opus 5 • medium • /effort` (bullet separator) | not ready (false negative) |
|
|
44
|
+
|
|
45
|
+
The artifact's own fixture contains `· /memory to free up context` and
|
|
46
|
+
`+1 more · /status` — **banner** lines in the exact shape it claimed prose never
|
|
47
|
+
uses. So discrimination was still vocabulary wearing structure's clothes, and the
|
|
48
|
+
line-start branch reinstated the Anthropic-copy dependency it claimed to remove
|
|
49
|
+
(at a fixed pane width, a copy edit shifting the wrap ~14 chars recreates the
|
|
50
|
+
original defect). A signal that cannot separate an *advert for* a command from a
|
|
51
|
+
*status bar showing* one carries no readiness information. Deleted, and the
|
|
52
|
+
genuinely structural markers widened instead.
|
|
53
|
+
|
|
54
|
+
**The answer is no longer a boolean.** See §1 — one of three consumers KILLS a live
|
|
55
|
+
session on not-ready. `classifyPaneReadiness` returns `ready | menu | not-ready`;
|
|
56
|
+
`isReadyPromptTail` is retained as the façade for callers that only want "can I
|
|
57
|
+
type now".
|
|
58
|
+
|
|
59
|
+
## Decision-point inventory
|
|
60
|
+
|
|
61
|
+
| point | classification | note |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `❯` / `›` present (and not on an option line) → ready | `invariant` | Deterministic substring test. |
|
|
64
|
+
| permission-mode / shortcut footer present → ready | `invariant` | Widened from one marker to four, sourced from the probes already trusted for this (`SessionReaper`, interactive-pool config) rather than invented here. |
|
|
65
|
+
| selector glyph ON a numbered option line + ≥2 options → menu | `invariant` | Deterministic shape test, mirroring `PermissionPromptAutoResolver`'s existing definition rather than a second divergent one. |
|
|
66
|
+
| slash-command mention → ready | **REMOVED** | Could not distinguish advert from status bar in any formulation tried. |
|
|
67
|
+
| consent-dialog auto-accept | `invariant` | Unchanged, and deliberately left in `SessionManager` — it has a side effect (`send-keys`), so it does not belong in a pure classifier. Verified still running BEFORE the classifier and over all 20 captured lines. |
|
|
68
|
+
|
|
69
|
+
No judgment points. No model call.
|
|
70
|
+
|
|
71
|
+
## 1. Over-block
|
|
72
|
+
|
|
73
|
+
**The risk is not uniform across callers, and the first draft of this section was
|
|
74
|
+
wrong about that.** There are three consumers:
|
|
75
|
+
|
|
76
|
+
1. `waitForClaudeReadyWithRetry` → `handleReadyAndInject` (spawn/inject). Failure
|
|
77
|
+
direction: waits, then the pre-existing extended-wait and blind-inject-if-alive
|
|
78
|
+
fallbacks apply. Safe.
|
|
79
|
+
2. `waitForClaudeReady` direct, in the Slack stuck-session path (`server.ts:8395`).
|
|
80
|
+
**Failure direction: it KILLS the live session and respawns.** A tightened probe
|
|
81
|
+
that false-negatives here destroys a live conversation. `docs/signal-vs-authority.md`
|
|
82
|
+
names session lifecycle as explicitly high-risk.
|
|
83
|
+
3. `classifyPaneState` (new), used by (2) to tell `menu` from `not-ready`.
|
|
84
|
+
|
|
85
|
+
**Mitigations, both required by the above:**
|
|
86
|
+
- The positive marker set was **widened**, not just narrowed: `? for shortcuts`,
|
|
87
|
+
`shift+tab to cycle`, `auto-accept edits` join `bypass permissions`. Previously
|
|
88
|
+
only one of Claude Code's three permission-mode footers was recognised, so a
|
|
89
|
+
session in auto-accept mode depended entirely on `❯` being visible. That
|
|
90
|
+
assumption is no longer load-bearing. All four are asserted absent from the
|
|
91
|
+
banner fixture, so the widening costs no false positives.
|
|
92
|
+
- The Slack path no longer kills on `menu`. It gives the always-on auto-resolver a
|
|
93
|
+
bounded second window and re-checks; a menu that never clears falls through to
|
|
94
|
+
the pre-existing stuck path, so no message is lost.
|
|
95
|
+
|
|
96
|
+
**Residual, stated honestly:** a status bar using a separator other than U+00B7 (`•`,
|
|
97
|
+
box-drawing) and showing none of the four footers and no `❯` would now wait. I have
|
|
98
|
+
not enumerated every Claude Code rendering across widths and themes. On caller (1)
|
|
99
|
+
that costs a delay; on caller (2) the menu carve-out plus the widened footers are
|
|
100
|
+
what keep it from costing a session.
|
|
101
|
+
|
|
102
|
+
## 2. Under-block
|
|
103
|
+
|
|
104
|
+
**Injection still reports success on having TYPED, not on arrival.**
|
|
105
|
+
`verifyInjection` recovers a swallowed *submit*, not a swallowed *paste*. A pane
|
|
106
|
+
that becomes ready and then stalls mid-paste still produces a success log for a lost
|
|
107
|
+
message. Deliberately **not bundled** — it changes the injector's success criterion
|
|
108
|
+
at every inject callsite, and folding it in would make this PR's refusal evidence
|
|
109
|
+
ambiguous. Recorded as a candidate, not silently deferred.
|
|
110
|
+
|
|
111
|
+
Also untouched: the probe reads the last 6 of 20 captured non-blank lines. A banner
|
|
112
|
+
longer than that window pushing a real prompt out of view still reads not-ready.
|
|
113
|
+
Unchanged behaviour; fails toward waiting on callers (1) and (3).
|
|
114
|
+
|
|
115
|
+
The menu detector requires ≥2 numbered options. A single-option prompt, or a
|
|
116
|
+
free-text startup question with no numbered options, is not detected as a menu.
|
|
117
|
+
|
|
118
|
+
## 3. Level-of-abstraction fit
|
|
119
|
+
|
|
120
|
+
One pure function, no state, no I/O, below `SessionManager`. The consent-dialog
|
|
121
|
+
branch stayed behind because it presses keys — a classifier that mutates the pane is
|
|
122
|
+
not a classifier.
|
|
123
|
+
|
|
124
|
+
**A smarter component already exists and was consulted rather than duplicated.**
|
|
125
|
+
`PermissionPromptAutoResolver` owns approval-prompt handling (matching, auto-answer,
|
|
126
|
+
audit). This probe deliberately reuses its notion of a menu (selector glyph on a
|
|
127
|
+
numbered option line) instead of inventing a second one, and does not attempt to
|
|
128
|
+
answer prompts — it only declines to call a menu an input surface.
|
|
129
|
+
|
|
130
|
+
Callers of the removed clause: exactly one, verified independently by grep. No
|
|
131
|
+
duplicate site left holding the old semantics.
|
|
132
|
+
|
|
133
|
+
## 4. Signal vs authority compliance
|
|
134
|
+
|
|
135
|
+
**The first draft's answer here was materially incomplete and is corrected.** It
|
|
136
|
+
claimed the probe "cannot block a message, refuse an action, or reach a user". False
|
|
137
|
+
via §1(2): a not-ready verdict at `server.ts` refuses an action and kills a session.
|
|
138
|
+
|
|
139
|
+
The *principle* is not violated — `docs/signal-vs-authority.md` scopes it to
|
|
140
|
+
brittle logic making judgments about **meaning**, and exempts deterministic
|
|
141
|
+
invariants. This is mechanics (is a glyph on screen), not meaning. But the honest
|
|
142
|
+
statement is: **this probe feeds a destructive authority**, which is exactly why the
|
|
143
|
+
verdict was widened from a boolean to a named state, so that authority can
|
|
144
|
+
distinguish "wedged" from "waiting on an answer" instead of treating both as kill.
|
|
145
|
+
|
|
146
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
147
|
+
|
|
148
|
+
No judgment points introduced. The change moves *away* from a vocabulary match whose
|
|
149
|
+
behaviour drifts as Anthropic edits its banner copy, toward structural markers. Note
|
|
150
|
+
the correction to the first draft: the intermediate "status-bar shape" design did
|
|
151
|
+
**not** achieve that and claimed it anyway — the claim is retracted above rather
|
|
152
|
+
than quietly restated.
|
|
153
|
+
|
|
154
|
+
## 5. Interactions
|
|
155
|
+
|
|
156
|
+
- **`waitForClaudeReadyWithRetry` / `handleReadyAndInject`** — unchanged; both now
|
|
157
|
+
consult the corrected probe. A pane that used to pass early on the banner now
|
|
158
|
+
passes on the input box instead: later, and correctly.
|
|
159
|
+
- **`server.ts` Slack stuck-session path** — changed, see §1.
|
|
160
|
+
- **`PermissionPromptAutoResolver`** — complementary, not shadowed. It clears
|
|
161
|
+
prompts; this declines to type at them. The Slack carve-out explicitly depends on
|
|
162
|
+
it running (it is an always-on floor with no enable flag).
|
|
163
|
+
- **`StuckInputSentinel`, `SessionWatchdog`, `PromptGate`, `PresenceProxy`,
|
|
164
|
+
`SessionReaper`, `TriageOrchestrator`, `anthropic-interactive-pool`** — each keeps
|
|
165
|
+
its own independent markers for its own question ("is it stuck?", "is it idle?").
|
|
166
|
+
None imports `detectClaudePrompt`; none carried the removed clause. Their marker
|
|
167
|
+
vocabulary is now the *source* for this probe's footers rather than a fourth
|
|
168
|
+
divergent set.
|
|
169
|
+
- **`ModelSwapService`** injects literal `/model <id>` into live panes — a real
|
|
170
|
+
in-pane source of the removed token. Another reason the clause had to go.
|
|
171
|
+
- **Codex / Gemini / pi panes** — the `›` clause is unchanged; they do not print the
|
|
172
|
+
Claude banner.
|
|
173
|
+
|
|
174
|
+
## 6. External surfaces
|
|
175
|
+
|
|
176
|
+
None. No route, no config key, no CLI flag, no message text, no state file. Two new
|
|
177
|
+
exported functions and one new public method on `SessionManager`, all internal.
|
|
178
|
+
|
|
179
|
+
## 6b. Operator-surface quality
|
|
180
|
+
|
|
181
|
+
One new log line when the Slack path declines to kill a session on a menu, naming
|
|
182
|
+
the session and the reason. The pre-existing failure lines are unchanged.
|
|
183
|
+
|
|
184
|
+
Worth recording for the follow-up: the *success* line still reports typing, not
|
|
185
|
+
arrival — the asymmetry that hid this incident for seven hours is untouched here.
|
|
186
|
+
|
|
187
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
188
|
+
|
|
189
|
+
Machine-local by construction — the probe reads a tmux pane on the machine that owns
|
|
190
|
+
it. No replication, no lease interaction, no shared state, no generated URL. Two
|
|
191
|
+
machines on different versions apply their own probe to their own panes; there is no
|
|
192
|
+
cross-machine invariant to violate.
|
|
193
|
+
|
|
194
|
+
## 8. Rollback cost
|
|
195
|
+
|
|
196
|
+
Low. Delete the module, restore the inline clauses, revert one `server.ts` block. No
|
|
197
|
+
migration, no persisted state, no config default, nothing written to an agent home.
|
|
198
|
+
The test file would fail on revert, which is the desired property.
|
|
199
|
+
|
|
200
|
+
## Phase 5 — Second-pass review (independent reviewer subagent)
|
|
201
|
+
|
|
202
|
+
**VERDICT: CONCERN** — one blocking, four should-fix, one note. Every finding was
|
|
203
|
+
independently verified before being accepted; all were real.
|
|
204
|
+
|
|
205
|
+
| # | finding | disposition |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| 1 (blocking) | Unenumerated third consumer at `server.ts:8395` kills a live session on not-ready, so §1's "the failure direction is waiting, which is safe" and §4's "cannot refuse an action" are false for it. | **Design changed.** Verdict widened to `ready \| menu \| not-ready`; Slack path given a menu carve-out + bounded re-check; §1 and §4 rewritten. |
|
|
208
|
+
| 2 (should-fix) | The `^` line-start branch reinstates the Anthropic-copy dependency via wrap geometry, and carries no test weight. | **Clause deleted entirely** (both branches). |
|
|
209
|
+
| 3 (should-fix) | "Shape, not vocabulary" is falsified by the artifact's own fixture; `· /model to opt in` matches. | **Verified and accepted.** Clause deleted; the claim is retracted in §4b rather than restated. |
|
|
210
|
+
| 4 (should-fix) | "A ready pane always shows `❯`" is contradicted by four sibling probes; only one of three permission-mode footers was covered. | **Marker set widened** to four footers, sourced from those siblings. This is also the mitigation for #1. |
|
|
211
|
+
| 5 (should-fix) | §4 Q4 materially incomplete. | **Rewritten** above. |
|
|
212
|
+
| 6 (note) | Interpunct is U+00B7 only; `•` and box-drawing are false negatives. `ModelSwapService` injects `/model` into live panes. | Moot for the separator (clause deleted); the `ModelSwapService` point is recorded in §5 as further justification. |
|
|
213
|
+
|
|
214
|
+
**Independently found by the operator, in parallel:** the menu case (§ Summary 2).
|
|
215
|
+
Verified against three realistic startup questions, all three of which read as ready
|
|
216
|
+
before this change.
|
|
217
|
+
|
|
218
|
+
**Reviewer concurrence on the revised design was not re-sought.** The revision was
|
|
219
|
+
driven by the reviewer's own findings plus a verified operator report, and every
|
|
220
|
+
change is pinned by a test asserting both sides of its boundary. That is a
|
|
221
|
+
disclosed reduction in independence for the second iteration, not a claim of
|
|
222
|
+
concurrence.
|