#648 の返信は来ていなかったが、high 4 件すべての箇所に手が入っていたので、新しい指摘を探すより直しの検証に充てた。6 観点 × 反証役、30 エージェント・エラー 0。確定 7 件 / 棄却 16 件 / 見落とし洗い出しで 4 件。
archived_seq — 3 文すべてに入っていた(upsertConnector 119 行 ・ touchConnector 126 行 ・ upsertCursor 155 行)。setCursor が書いた行が読めるようになり、getCursor(...) ?? getMaxSeq(...) が「毎回いまの最大値」を受けることは無くなった。archived_seq = NULL を書く箇所は src 配下で 4 か所だけで、自動で解除が走る経路もない" による href からの抜け出し — 塞がっている。escapeText(18-24 行)が " を " に置換し、順序も正しい(" が & < > の後)。' は自動リンクの文字集合(51 行)から外れ、単引用符で囲む属性を作っている箇所もない。href に空白を混ぜる経路も U+00A0 ・ U+2028 まで実測して無しrestore.ps1 の印 — finally は消え、印を消すのは try の末尾(239-241 行)だけ。失敗経路の catch は印を残すので、chat.db が無い状態で印が外れる経路は閉じているissues.html — 更新日が 2026-09-08 に。4 グループとも件数・内訳が中身と一致(g01 5 / g02 6 / g03 20 / g04 20)。id 51 件で重複なし、i260908-01〜05 の連番も飛びなし| — 全 md に及んでいて残りゼロ。表の外の <code> 内の |(4 箇所)を過剰にエスケープしてもいない。md 側に記号のまま残った ** も 0 件(22 md を CommonMark の左右フランキングで判定)restore.ps1 は [0/5] で必ず落ちる。--connector-id の直しでは動くようにならないtools/80_ops/restore.ps1:112(関連 37 ・ 116-119 ・ 124 ・ 145)
37 行の $ErrorActionPreference = 'Stop' のもとで、112 行の & node $clientPath say … 2>&1 を try の外で受けている。5.1 では外部コマンドが stderr に 1 行書くだけで NativeCommandError が終端エラーになる。node 側の announceEnv は繋ぐコマンドすべてで環境名を必ず stderr に書くので(chat.mjs:1298)、この行は毎回落ちる。
[0/5] 参加者に知らせる
→ NativeCommandError の赤字で exit 1。印を置く 124 行まで進まない
--connector-id は直っているが、落ちる理由が入れ替わっただけ。「すでに止まっている可能性」を扱う 116-119 行の分岐も到達しない。DB は壊れない(印より前で落ちるので fail-safe)が、復旧手順が 1 行も進まない。
issues.html:898 は「廃止した --connector-id を使っていた 2 行も直した。直す前は exit 2 で最初の 1 行から進まなかった」を ☑ 済として記録している。exit 2 が exit 1 になっただけで、症状は同じ。#19 で出した backup.ps1:72 の 2>&1 と同じ形。
archives の行だけが消えるstore.mjs 400-408(404 行の UPDATE と 407 行の DELETE)
archived_seq = NULL を 3 文に足したが、restore() は 1 行も直っていない。復活した行は archived_seq が NULL なので 404 行の WHERE archived_seq = ? から外れる。それでも 407 行の DELETE FROM archives は restored を見ずに同じトランザクションで走り、server.mjs:606-607 も戻り値の有無だけを見て 200 を返す。
発言なしで connector を片付けた archive は connectors 1 行 + cursors n 行だけなので(368-377 行)、その参加者が繋ぎ直して読み直せば全行が復活し、restore が完全な空振りになる。失われるのは archives の 1 行(時刻・実行者・description)で、2 回目は 400 が返るためその番号は二度と使えない。
画面から戻したときだけ完全に無言。chat.js:630-635 は応答を受け取らず 646-648 行で閉じて読み直すだけなので restored が出ない(CLI は「(0 件)」を印字する)。chat.js:616 のコメント「戻すのは元に戻すだけで失われるものが無いため」が、この経路で成り立たない。
採否の理由も両案を区別できていない。issues.html:1264 は「行を消して入れ直す案は採らない。restore() が archived_seq でまとめて戻すため、行を消すと戻せなくなる」と書くが、archived_seq = NULL はその行を restore() の射程から外す点で削除と同じ結果。加えて upsertCursor は msg_seq = excluded.msg_seq も当てるので、片付けた時点の読んだ位置は復活の瞬間に上書きされ、restore() を直しても取り戻せない。
< と > を許すため、生成済みタグを href に飲み込むmarkdown.js:51 の [^\s&"']+
< > が残っているため、42-43 行が先に生成した <code> <strong> と、57-59 行が戻すコードブロックのタグが URL に飲み込まれる。入力に含まれる生のタグは < になって & で切れるので無害で、飲み込まれるのは自前が生成したタグだけ。属性からの抜け出しは起きない。
URL をインラインコードや太字で囲む形で発現する。遷移先が本文に書いた URL と別物(タグ文字列を含む相対 URL)になり、取り残された閉じタグで後続の文が太字・等幅になる。
markdown.js の NUL 1 バイトが残り、この修正が git diff にも grep にも出ないmarkdown.js:27(バイトオフセット 1171)の PLACEHOLDER にバイト値 0 が 1 文字リテラルで置かれたままで、今回の修正コミットでも触られていない。
このため git はこのファイルを -text と判定し続ける(git ls-files --eol が i/-text w/-text。他の追跡テキストは i/lf)。
git diff --stat → Bin 2148 -> 2696 bytes
git diff → Binary files … differ
git diff --numstat → - -(行数の集計に乗らない)
ripgrep → PLACEHOLDER の定義がある 27 行が落ちる(38・57 行だけ返る)
レビューもコミット前確認も、XSS を止めている唯一のファイルだけを素通りする。次に escapeText や 51 行の文字集合が 1 文字戻っても diff に出ず、grep は 0 件を返す(0 件が正常に見える形の失敗)。merge も 3-way ができない。git blame は効く。
エスケープ記法(バックスラッシュ + u0000)で書けば挙動は同じでテキストのままだった。あわせて -text 扱いのため EOL 正規化からも外れ、clone しても markdown.js だけ LF のまま残る。
.editorconfig ・ .gitattributes ・ .vscode/settings.json が 3 点とも無く、core.autocrlf = true が利用者の全体設定に入っている。索引側の .mjs は LF で、src/web/js/ は chat.js が CRLF ・ stale.js と markdown.js が LF と混ざっている。
TryGet が Environment.Exit を通すので、印を出すだけの問い合わせで全体が exit 1 になるsrc/cli-cs/Client.cs:70(関連 66-76 ・ 166-169、Program.cs:120 ・ 170-171)
TryGet は catch (Exception) で包んで null を返す作りだが、呼んでいる SendOnce は 503 以外の HTTP エラー応答で Environment.Exit(1) を呼ぶ(169 行)。Environment.Exit は例外ではないので catch を通り抜ける。Get の Exit(3) は避けられたが、Exit(1) の経路は残っている。
--port / --url が aichat 以外の HTTP サーバー(別プロジェクトの開発サーバー・残ったプロセス・プロキシ)に当たると、/api/version の 404 等で C# 版だけが exit 1 で終わり、打ったコマンドが 1 度も走らない。node 版は announceEnv を黙って飛ばして本来の呼び出しに進み、そこで正しい理由を出す。ポートの取り違えを知らせるための仕掛けが、取り違えたときに理由を隠す形。
server.mjs:752。打ち切りの判定が「次にまだ残っているか」ではなく「今までに送った数」だけを見るため、滞留がちょうど SSE_CATCHUP_MAX(2000)= MAX_HISTORY_LIMIT(500)× 4 のとき、4 周目で全部送り終えているのに truncated を送る。書きかけの本文が消えて履歴が直近 50 件に戻るtruncated を通るテストが無い — long-poll 側の直しには回帰テストを足したのに(tests/server.test.mjs:169-194)、/api/events を叩く唯一のテストは since=99999 を渡すので server.mjs:746 で即抜け、追加した経路に一度も入らないtouchConnector を踏むテストが 1 つも無い — 追加分(tests/archive.test.mjs:195-264)は joinConnector と setCursor だけを踏み、tests/store.test.mjs:105・111 の touchConnector のテストは archive を挟まない。330-356 行の SQL 検査も /SELECT/i で絞るので INSERT … ON CONFLICT を見ていない。join を経ず say ・ poll だけで片付けた参加者が戻る経路(本番で最も通る)が無検査mask-log-input.ps1:26 が子の終了コードを見ない — 全 37 行で $LASTEXITCODE の参照が 0 箇所。本体のログが無い経路で mask-log.ps1 が 117 行で exit 1 しても親は止まらず、総括 1 行が欠けたまま 29 行の確認に進むmarkdown.js:26 のコメントが事実と違う — 「本文に現れない制御文字を使う」と書いているが、msg_body はその文字を素通しする(requireBody は文字数上限だけ、addMessage も加工しない)。本文にその文字を 2 つ挟んで数字を囲む形を含めると、57-59 行の復元がその位置を blocks[n] に置き換えるため、書いていないコードブロックが複製される、または undefined が出る。退避した中身は既にエスケープを通っているので注入にはならないが、コメントを信じて次の改修で本文の検査を省くと、条件が変わったことに気づけない棄却 16 件は、ほぼすべて「今日の直しで既に手当てされていた」もの。検証を各観点の上位 4 件で打ち切ったため 6 件が未検証。テストとサーバーは実行せず、DB ファイルも開いていない。#648 で出したまま手つかずのもの(90 秒オフライン ・ メンテ帯 ・ formnovalidate ほか)は再送していない。
課題化済み#670(2026-09-08 21:00)で次のとおり受け取りを確認している。
「確認しました。high 1 件は i260908-04 に折り込みました(--connector-id の直しは exit 2 が exit 1 になっただけで、実は塞がっていませんでした。記録を訂正しました)。medium 4 件・low 5 件は i260908-07 として新規に立てました。直すかどうかは追って判断します。」
high 1 件(restore.ps1 の [0/5] 停止)は既存課題 i260908-04 に統合され、記録も訂正済み。medium 4 件・low 5 件は新規課題 i260908-07 として登録済みで、この記録の時点では修正の実施までは確認していない(登録のみ)。