fix: address remaining minor code review findings (FAR-15)

- #9: match Paperclip container by name in k8s-client instead of
  trusting spec.containers[0], which could be a service-mesh sidecar
- #11: key assistant-text dedup by (message.id, index) so legitimate
  duplicate content across turns isn't collapsed in the summary
- #16: trim trailing hyphens from sanitized K8s names so truncation
  doesn't produce names ending in "-"

Findings #5 (keepalive re-verify) and #6 (one-shot log dedup) were
already addressed in the current code — verified during this review.
#8 (orphan reattach behavior) requires a product decision on whether
"new session wins" is intentional, so deferring.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Gandalf the Greybeard
2026-04-23 23:34:59 +00:00
parent 21a02da00f
commit 98f3821f91
3 changed files with 24 additions and 8 deletions
+15 -6
View File
@@ -9,9 +9,12 @@ export function parseClaudeStreamJson(stdout: string) {
let model = "";
let finalResult: Record<string, unknown> | null = null;
const assistantTexts: string[] = [];
// Belt-and-braces dedup: track seen text blocks to filter duplicates
// caused by log stream reconnects replaying overlapping windows.
const seenTexts = new Set<string>();
// Belt-and-braces dedup: key by (message.id, textIndex) so a session that
// legitimately emits the same text twice in different turns isn't collapsed
// (finding #11, FAR-15). The log-dedup filter handles reconnect overlaps
// at the line level; this guard only needs to protect against the same
// message block being parsed twice.
const seenBlocks = new Set<string>();
for (const rawLine of stdout.split(/\r?\n/)) {
const line = rawLine.trim();
@@ -29,14 +32,20 @@ export function parseClaudeStreamJson(stdout: string) {
if (type === "assistant") {
sessionId = asString(event.session_id, sessionId ?? "") || sessionId;
const message = parseObject(event.message);
const messageId = asString(message.id, "");
const content = Array.isArray(message.content) ? message.content : [];
for (const entry of content) {
for (let i = 0; i < content.length; i++) {
const entry = content[i];
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
const block = entry as Record<string, unknown>;
if (asString(block.type, "") === "text") {
const text = asString(block.text, "");
if (text && !seenTexts.has(text)) {
seenTexts.add(text);
if (!text) continue;
// Prefer (messageId, index) when the message has an id; fall back
// to text content when it doesn't (legacy/partial events).
const key = messageId ? `${messageId}:${i}` : `text:${text}`;
if (!seenBlocks.has(key)) {
seenBlocks.add(key);
assistantTexts.push(text);
}
}