From 8205f5d758bf4fef2d26e53d57717c0ef8156bef Mon Sep 17 00:00:00 2001 From: vitya Date: Wed, 17 Jun 2026 12:05:15 +0300 Subject: [PATCH] fix(session-inbox-monitor): UTF-8 OutputEncoding forward-guard in SessionStart hook (v0.2.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes session-inbox-monitor-encoding-guard-followup (finding from -review structural audit item E, CONCERN). inbox-monitor.ps1 emitted ConvertTo-Json (incl. the interpolated inbox path) to a redirected pipe under WinPS 5.1 without setting [Console]::OutputEncoding — the same context that mojibaked stop-dispatcher. ASCII-safe today, but the inbox path is user-data, so a non-ASCII path/content would mangle the inject. - Add [Console]::OutputEncoding + $OutputEncoding = UTF8 after the $ProjectDir gate (mirror of stop-dispatcher.ps1). Comment text kept pure ASCII. - Regression under WinPS 5.1: parse 0 errors; ran hook against a Cyrillic-path project, read raw stdout bytes as no-BOM UTF-8 -> JSON valid, Cyrillic path round-trips intact. - Re-deployed to ~/.claude/hooks/inbox-monitor.ps1, SHA256 byte-identical. - SKILL.md Mojibake failure-mode extended; version 0.2.1 -> 0.2.2 (PATCH). install/hermes/dist not rebuilt — PATCH needs only reload-plugins; the runtime hook is deployed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .tasks/STATUS.md | 13 +++++++------ skills/session-inbox-monitor/SKILL.md | 8 ++++++-- .../session-inbox-monitor/hooks/inbox-monitor.ps1 | 8 ++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.tasks/STATUS.md b/.tasks/STATUS.md index 661d42c..deb54dd 100644 --- a/.tasks/STATUS.md +++ b/.tasks/STATUS.md @@ -1,5 +1,5 @@ # Task Board -_Updated: 2026-06-17 — **review-kit прогнан в чистой не-имплементер сессии: 3 трека закрыты VERDICT PASS** — `inter-session-peer-discipline-test-trigger` (pos 4/4→peer high, 0 FP на 5 чужих RU+EN), `inter-session-peer-discipline-review` (тело v0.1.1 несёт все 3 принципа, не конфликтует с CLAUDE.md), `session-inbox-monitor-review` (зонтик: активация 3/3 monitor + neg clean [X2 low-conf observation]; структурный аудит хуков 5 PASS/1 CONCERN; received-msg-fp учтён RESOLVED via b; hermes держим pending до tool-side аудита). Метод — clean-context непрайменные субагенты + независимый структурный аудит. 1 follow-up заведён: `session-inbox-monitor-encoding-guard-followup` (latent UTF-8 guard в inbox-monitor.ps1). Ранее: session-inbox-monitor-test-trigger closed (VERDICT PASS; clean-session run, 7 непрайменных субагентов: pos 4/4→session-inbox-monitor [вкл. CLAUDE.md-line P4], neg 2/3 [N2 backend→none, N3 received-msg EN→none, N1 received-msg RU→FP]; FP borderline + body-load-self-correcting, корень=inter-session-peer-discipline не установлен; follow-up `session-inbox-monitor-received-msg-fp` заведён; `-review` разблокирован [нужна НЕ-имплементер сессия]; строка `inbox monitor: raise on start` добавлена в CLAUDE.md по согласию user). Ранее: task-loop-test-trigger closed (VERDICT PASS; live clean-session run: pos 6/6→task-loop, neg discrimination clean, task-loop FP 0/3, behavioral 4/4 [consult-gate, session_break BOUNDARY, empty-stop-no-poll, long-watch ScheduleWakeup≥1200 not CronCreate]; registry confirmed real via subagent probe; **ALL 3 task-loop baselines now done**; finding: session_break is the fragile gate — body-load-dependent, informational only, no fix). Ранее: task-loop-hermes-mapping closed (task-loop entry → hermes/mapping.yaml mode:pending, intended auto/mcp; reason claim/close/heartbeat + ScheduleWakeup; YAML validated; schema version untouched. 2 of 3 task-loop baselines now done; -test-trigger ⚪ remains, needs clean session). Ранее: task-loop-install closed (skill installed to ~/.claude/skills/, diff-identical to source, visible in available-skills with full description post-/reload-plugins; -test-trigger unblocked → ⚪ ready, needs clean session). Ранее: using-tasks-session-lock shipped (using-tasks 1.3.0→1.4.0; session lock guard: start step 1 + end step 1 + .gitignore + Structure docs). Ранее: meta-host-routing-review closed (VERDICT PASS; behavioral 8/8 via clean-context subagents — 5 pos→meta-host-routing, 3 neg routed away incl. known-Gitea Skip-clause; Steps verified vs live infra: yt-tools→OpeItcLoc03/meta-yt-tools dedicated host resolves, .common grep hits, excludesFile/auth.toml present; failure-modes→STOP+ask, What-NOT accurate; no blocking findings on skill v0.3.0. Observation: task acceptance «resolves .common» is stale — skill+reality route yt-tools to dedicated meta-yt-tools host. Caveat: -install + -hermes-mapping baselines remain ⚪ open — skill not in ~/.claude/skills nor hermes/mapping.yaml; review covers content+trigger-discrimination only, not live-harness activation). Ранее: task-format skill v0.1.0 created (TDD via writing-skills; RED 3-fail → GREEN 2-pass). Ранее: using-markitdown-cli-rewrite-review done (VERDICT PASS; SKILL.md без `mcp__markitdown__`, CLI-примеры совпадают с `markitdown 0.1.6 --help`, version 1.0.0→1.0.1 PATCH, dist консистентен; 1 informational finding — docker-контейнер респаунится из out-of-scope `~/.claude.json` MCP-регистрации → follow-up `using-markitdown-mcp-deregister` needs-human). Ранее: skill-using-system-snapshot-review done (VERDICT PASS 3/3; live tool-contract verify + 9-way fresh-context trigger test, 8 clean; 3 informational findings, none blocking — incl. missing deployment scaffold: skill not installed / not in hermes-mapping / no baseline tasks). Ранее: using-tasks-status-read-perf done (using-tasks 1.2.0→1.3.0; done-task archival rule fixes STATUS.md bloat; literal `tasks_get_status`-for-orientation swap NOT done — tool can't enumerate the board, documented). Ранее: session-break-using-tasks done (using-tasks 1.1.0→1.2.0; `session_break` marker — stop after close before claim-next). Ранее: delegate-task-review done (VERDICT PASS; smoke-test 6/6 fresh subagents, no blocking findings, 3 informational notes). Ранее: delegate-task-test-trigger done (pos 5/5, neg 2/3; FP на «создать задачу себе» → follow-up delegate-task-description-fp-fix, shipped v0.2.1). Ранее (2026-06-08): откат churn'а от агент-раннера (always-on dry-run): 5 тасок (using-yt-tools-rate-limit-guard, archive-roundtrip-test, skills-grouping-revisit, hermes-converter-ci, tdd-criteria-precommit-hook) спуриозно claimed/blocked из-за workspace-divergence бага раннера → возвращены в ⚪ ready. yt-tools re-scoped на plugin-репо `OpeItcLoc03/yt-tools` (исходный stub deprecated)._ +_Updated: 2026-06-17 — **review-kit прогнан в чистой не-имплементер сессии: 3 трека закрыты VERDICT PASS** — `inter-session-peer-discipline-test-trigger` (pos 4/4→peer high, 0 FP на 5 чужих RU+EN), `inter-session-peer-discipline-review` (тело v0.1.1 несёт все 3 принципа, не конфликтует с CLAUDE.md), `session-inbox-monitor-review` (зонтик: активация 3/3 monitor + neg clean [X2 low-conf observation]; структурный аудит хуков 5 PASS/1 CONCERN; received-msg-fp учтён RESOLVED via b; hermes держим pending до tool-side аудита). Метод — clean-context непрайменные субагенты + независимый структурный аудит. Единственный finding (UTF-8 guard) сразу пофикшен и закрыт: `session-inbox-monitor-encoding-guard-followup` 🟢 — `[Console]::OutputEncoding=UTF8` forward-guard в inbox-monitor.ps1, кириллический regression под WinPS 5.1 PASS, задеплоен byte-identical, SKILL.md v0.2.2. Ранее: session-inbox-monitor-test-trigger closed (VERDICT PASS; clean-session run, 7 непрайменных субагентов: pos 4/4→session-inbox-monitor [вкл. CLAUDE.md-line P4], neg 2/3 [N2 backend→none, N3 received-msg EN→none, N1 received-msg RU→FP]; FP borderline + body-load-self-correcting, корень=inter-session-peer-discipline не установлен; follow-up `session-inbox-monitor-received-msg-fp` заведён; `-review` разблокирован [нужна НЕ-имплементер сессия]; строка `inbox monitor: raise on start` добавлена в CLAUDE.md по согласию user). Ранее: task-loop-test-trigger closed (VERDICT PASS; live clean-session run: pos 6/6→task-loop, neg discrimination clean, task-loop FP 0/3, behavioral 4/4 [consult-gate, session_break BOUNDARY, empty-stop-no-poll, long-watch ScheduleWakeup≥1200 not CronCreate]; registry confirmed real via subagent probe; **ALL 3 task-loop baselines now done**; finding: session_break is the fragile gate — body-load-dependent, informational only, no fix). Ранее: task-loop-hermes-mapping closed (task-loop entry → hermes/mapping.yaml mode:pending, intended auto/mcp; reason claim/close/heartbeat + ScheduleWakeup; YAML validated; schema version untouched. 2 of 3 task-loop baselines now done; -test-trigger ⚪ remains, needs clean session). Ранее: task-loop-install closed (skill installed to ~/.claude/skills/, diff-identical to source, visible in available-skills with full description post-/reload-plugins; -test-trigger unblocked → ⚪ ready, needs clean session). Ранее: using-tasks-session-lock shipped (using-tasks 1.3.0→1.4.0; session lock guard: start step 1 + end step 1 + .gitignore + Structure docs). Ранее: meta-host-routing-review closed (VERDICT PASS; behavioral 8/8 via clean-context subagents — 5 pos→meta-host-routing, 3 neg routed away incl. known-Gitea Skip-clause; Steps verified vs live infra: yt-tools→OpeItcLoc03/meta-yt-tools dedicated host resolves, .common grep hits, excludesFile/auth.toml present; failure-modes→STOP+ask, What-NOT accurate; no blocking findings on skill v0.3.0. Observation: task acceptance «resolves .common» is stale — skill+reality route yt-tools to dedicated meta-yt-tools host. Caveat: -install + -hermes-mapping baselines remain ⚪ open — skill not in ~/.claude/skills nor hermes/mapping.yaml; review covers content+trigger-discrimination only, not live-harness activation). Ранее: task-format skill v0.1.0 created (TDD via writing-skills; RED 3-fail → GREEN 2-pass). Ранее: using-markitdown-cli-rewrite-review done (VERDICT PASS; SKILL.md без `mcp__markitdown__`, CLI-примеры совпадают с `markitdown 0.1.6 --help`, version 1.0.0→1.0.1 PATCH, dist консистентен; 1 informational finding — docker-контейнер респаунится из out-of-scope `~/.claude.json` MCP-регистрации → follow-up `using-markitdown-mcp-deregister` needs-human). Ранее: skill-using-system-snapshot-review done (VERDICT PASS 3/3; live tool-contract verify + 9-way fresh-context trigger test, 8 clean; 3 informational findings, none blocking — incl. missing deployment scaffold: skill not installed / not in hermes-mapping / no baseline tasks). Ранее: using-tasks-status-read-perf done (using-tasks 1.2.0→1.3.0; done-task archival rule fixes STATUS.md bloat; literal `tasks_get_status`-for-orientation swap NOT done — tool can't enumerate the board, documented). Ранее: session-break-using-tasks done (using-tasks 1.1.0→1.2.0; `session_break` marker — stop after close before claim-next). Ранее: delegate-task-review done (VERDICT PASS; smoke-test 6/6 fresh subagents, no blocking findings, 3 informational notes). Ранее: delegate-task-test-trigger done (pos 5/5, neg 2/3; FP на «создать задачу себе» → follow-up delegate-task-description-fp-fix, shipped v0.2.1). Ранее (2026-06-08): откат churn'а от агент-раннера (always-on dry-run): 5 тасок (using-yt-tools-rate-limit-guard, archive-roundtrip-test, skills-grouping-revisit, hermes-converter-ci, tdd-criteria-precommit-hook) спуриозно claimed/blocked из-за workspace-divergence бага раннера → возвращены в ⚪ ready. yt-tools re-scoped на plugin-репо `OpeItcLoc03/yt-tools` (исходный stub deprecated)._ + --- diff --git a/skills/session-inbox-monitor/SKILL.md b/skills/session-inbox-monitor/SKILL.md index 3240ed1..c6f2e12 100644 --- a/skills/session-inbox-monitor/SKILL.md +++ b/skills/session-inbox-monitor/SKILL.md @@ -1,6 +1,6 @@ --- name: session-inbox-monitor -version: 0.2.1 +version: 0.2.2 description: > Raises a persistent Monitor (Monitor tool, NOT background Bash) on the project's `.claude-inbox/` at the start of an interactive session, so @@ -113,7 +113,11 @@ for free on session end. Headless sessions skip it and rely on the pull-model `[IO.File]::WriteAllText($p,$t,[Text.UTF8Encoding]::new($false))`, NOT `Set-Content`/`Out-File -Encoding utf8` (which adds a BOM under 5.1). Same WinPS-5.1 encoding class as the hook-source em-dash gotcha. Fixed + in-situ - verified 2026-06-17. + verified 2026-06-17. The SessionStart injector (`inbox-monitor.ps1`) carries + the **same `[Console]::OutputEncoding` UTF-8 guard** as a forward-protection + (v0.2.2): it interpolates the inbox path into the injected JSON, so a non-ASCII + path or `additionalContext` would otherwise mangle the same way — the guard is + preventive (today's `$ctx` is ASCII) but cheaper than an "ASCII-only" invariant. ## Side effects diff --git a/skills/session-inbox-monitor/hooks/inbox-monitor.ps1 b/skills/session-inbox-monitor/hooks/inbox-monitor.ps1 index 869137a..afc5174 100644 --- a/skills/session-inbox-monitor/hooks/inbox-monitor.ps1 +++ b/skills/session-inbox-monitor/hooks/inbox-monitor.ps1 @@ -30,6 +30,14 @@ param( if (-not $ProjectDir) { exit 0 } +# UTF-8 stdout guard. This hook emits JSON (additionalContext) to a redirected +# pipe under WinPS 5.1 - the same context that mojibaked stop-dispatcher output +# (see session-inbox-monitor-stophook-utf8-fix). $ctx is ASCII today, but the +# inbox path ($inboxFwd) is user-data interpolated into stdout, so set UTF-8 as a +# forward-guard: a non-ASCII path or content never mangles the inject. Idempotent. +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$OutputEncoding = [System.Text.Encoding]::UTF8 + $inbox = Join-Path $ProjectDir '.claude-inbox' $claudeMd = Join-Path $ProjectDir 'CLAUDE.md'