2026-06-25
pi slash commands were dead — sendMessage without triggerTurn (#217)
The pi-agent ports (plugins/pi-memory, plugins/pi-knowledge, added #211) registered
their slash commands fine but nothing happened when you ran one. Root cause: the command
handlers inject the bundled skill body via pi.sendMessage({…, display:false}) with no
options. Pi's sendCustomMessage only starts an LLM turn when triggerTurn:true (or while
streaming, via steer/followUp); on an idle session — exactly when you type a slash command —
it just appends the message to history and fires no turn. So the instructions landed in
context and the agent never acted. Fix: pass { triggerTurn: true } in both command
handlers. Bumped pi-memory 0.12.2→0.12.3, pi-knowledge 0.5.2→0.5.3, added CHANGELOGs.
Full gotcha in /debugging.md. Shipped as #217.
Cleared demarkus-plugin golangci-lint debt (#218)
pre-commit.sh had been failing on tools/ for a while — 63 pre-existing findings in the
demarkus-plugin Go module (errcheck, revive, gocritic, modernize), unrelated to the pi
work. Cleared all of them: golangci-lint run --fix handled the modernize/gocritic-auto set
(range-over-int, slices.Contains, SplitSeq, CutPrefix, regex \d, http.NoBody); the rest by
hand — wrapped deferred cleanups defer func(){ _ = … }(), discarded error-path
Close/Remove with _ =, reformatted exported doc comments (dropped the Name: colon form
revive rejects), *Input for the heavy nudge funcs, and extracted runLocked /
runProvisionLocked to kill the two deferInLoop warnings while keeping panic-safe lock
release. No behavior change. All four modules now lint 0 issues.
Poison-lock fix from the #218 review (CodeRabbit)
Addressing the PR review surfaced a real latent bug in both mkdir-mutex lock acquirers
(withProvisionLock, withLock): they ignored the os.WriteFile error when stamping the
holder's PID into the lock dir. A failed stamp leaves an unowned lock that stale-lock
recovery can never reclaim (it reads that PID to prove the holder is dead) → wedges every
waiter until timeout. Now: release the lock dir and fail. Detail in /debugging.md. Confirmed
only two mkdir-lock sites exist module-wide; the other os.WriteFile calls are either
already checked or intentional best-effort sentinels (guidance.go offer markers).
Housekeeping
Both branches squash-merged (#217, #218). Pruned local branches 177→7: 17 by empty-diff vs
main, 153 by matching a merged PR head ref (gh pr list --state merged --json headRefName —
the authoritative signal that catches squash-merges git can't detect locally). Kept main,
the three pages* branches (no merged PR matched), and the release-please automation
branches.
Client conn-pool wedge: diagnosed, fixed, released (#219 → #220 → #221)
The soul server timing out mid-session (open stream: context deadline exceeded on
every fetch after a successful mark_graph crawl) turned out to be a client-side
bug, not server or data. Origin: client/fetch/fetch.go requestOnConn →
conn.OpenStreamSync(ctx) hits the 10s RequestTimeout and returns a wrapped
context.DeadlineExceeded. The pooled QUIC connection had gone bad for new streams
(server away / restart / path drop) while keepalive kept conn.Context() un-cancelled,
so getConn reused it. Recovery should have evicted+redialed — but isTimeoutError /
isTemporaryError did a bare type assertion on the wrapped error, which a
*fmt.wrapError doesn't satisfy, so the timeout was misclassified non-transient,
doWithRetry never called removeConn, and every subsequent request reused the
poisoned connection. Persistent wedge until the MCP server restarts. (Full writeup in
/debugging.md.)
Fix: errors.As in both classifiers so they unwrap to the underlying timeout →
evict+redial self-heals in ~100ms. Regression test added (a wrapped
context.DeadlineExceeded must read transient). Shipped #219.
Release chain to actually reach users (the fix is in demarkus-mcp, which the plugins
provision at a pinned version):
- client/v0.13.2 — auto-released on #219 merge (binaries with the fix).
- #220 — bumped
clientVersion0.13.1→0.13.2 inprovision.go→ auto-released tools/v0.3.5 (thedemarkus-pluginthat provisions the fixeddemarkus-mcp). - #221 — repinned
TOOLS_VERSION0.3.3→0.3.5 in all four plugins'bootstrap.sh(they share one binary, so lockstep) + version bumps: demarkus-memory 0.12.3, demarkus-knowledge 0.5.3, pi-memory 0.12.4, pi-knowledge 0.5.4. On next session start, installs re-provision v0.3.5 → demarkus-mcp v0.13.2 → wedge gone.
Lesson reinforced: a _ =-free but bare type assertion is as much an unwrap bug as
a swallowed error — wrapped errors need errors.As/errors.Is. And the demarkus
release topology is three-legged: client binary (auto on commit) → demarkus-plugin
clientVersion pin (auto tools release) → plugin bootstrap TOOLS_VERSION repin (reaches
users). All three legs are required; the first two are automatic, the third is manual.