Two known sharp edges shipped (documented, not fixed) in 0.2.0. Filing so they're tracked rather than living only in local notes. Both are conscious ship-with decisions, not regressions.
1. messagesSince("0") slices the entire transcript (read-side cursor footgun)
send() was hardened to never return "0" (it returns the DELIVERY_UNCONFIRMED / DELIVERED_QUEUED sentinels), but the read side still honors a numeric/positional cursor: messagesSince("0") → all.slice(0) = every message ever (src/session/handle.ts, the messagesSince resolver, ~L242).
- No current code path feeds it
"0", so it's defanged in practice — but it's a live trap: a consumer who stores or derives a numeric cursor gets the whole conversation back, which looks like a flood of duplicates or causes re-acting on old turns.
- Its only writeup currently lives in local design notes (the F40 entry, now outside the repo), so it's effectively invisible to consumers.
Fix options (the "option 3" discussed):
2. Permission-prompt is inert under bypassPermissions / acceptEdits
- The S5 approve/deny path (
wait() → awaiting{permission-prompt}, state() → permission-prompt, respond()) only fires when claude runs in a prompting mode (default / plan / ask). Under bypassPermissions (or acceptEdits for edits) claude runs tools un-gated — no prompt — and claudemux correctly reports completed.
- This bites the likely primary user: an unattended daemon usually runs
bypassPermissions so it doesn't block on prompts — and therefore never sees the gate it might be relying on for programmatic approve/deny.
- Documented in the README permission-mode caveat; not solved at the API level.
Open design question:
Neither blocks 0.2.0. Tracking only.
Two known sharp edges shipped (documented, not fixed) in 0.2.0. Filing so they're tracked rather than living only in local notes. Both are conscious ship-with decisions, not regressions.
1.
messagesSince("0")slices the entire transcript (read-side cursor footgun)send()was hardened to never return"0"(it returns theDELIVERY_UNCONFIRMED/DELIVERED_QUEUEDsentinels), but the read side still honors a numeric/positional cursor:messagesSince("0")→all.slice(0)= every message ever (src/session/handle.ts, themessagesSinceresolver, ~L242)."0", so it's defanged in practice — but it's a live trap: a consumer who stores or derives a numeric cursor gets the whole conversation back, which looks like a flood of duplicates or causes re-acting on old turns.Fix options (the "option 3" discussed):
"0"stops silently meaning "everything".turns()/ a no-cursormessages) so there's a sanctioned way to read history / recover cursors.2. Permission-prompt is inert under
bypassPermissions/acceptEditswait() → awaiting{permission-prompt},state() → permission-prompt,respond()) only fires when claude runs in a prompting mode (default/plan/ask). UnderbypassPermissions(oracceptEditsfor edits) claude runs tools un-gated — no prompt — and claudemux correctly reportscompleted.bypassPermissionsso it doesn't block on prompts — and therefore never sees the gate it might be relying on for programmatic approve/deny.Open design question:
permissionModeto a first-classCreateOptions/ResumeOptionsfield instead of threading it throughextraArgs, so the gate's prerequisite is discoverable.Neither blocks 0.2.0. Tracking only.