# 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 `clientVersion` 0.13.1→0.13.2 in `provision.go` → auto-released **tools/v0.3.5** (the `demarkus-plugin` that provisions the fixed `demarkus-mcp`). - **#221** — repinned `TOOLS_VERSION` 0.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.