Conversation
… fix - Knowledge history via SQLite UPDATE/DELETE triggers; timeline + as_of queries - Temporal facts (entity/relation/value/valid_from/valid_to); one active per pair - MCP tools: add_fact, query_facts, close_fact, fact_history - execute_command wall-clock timeout (default 90s, config 0=off) - Fix Cancel button: CallbackQuery bypasses per-chat serialization Deferred: agentic RAG (#52), harness evolution (#53)
Trigger anomalyco/opencode on issue and PR review comments starting with /oc or /opencode.
Runs opencode on opened/synchronize/reopened/ready_for_review without needing /oc.
|
Both axes' key claims verified against source ( StandardsSQL injection: none found. All new queries in Security (timeout/cancel): Default 90s cap tightens sandbox behavior; Documented-standard issues
Baseline smells (judgement calls)
Spec(a) Missing / partial
(b) Scope creep
(c) Implemented but looks wrong
Summary: Standards — 6 findings, worst: unpinned |
- Register cancel before showing Cancel button (race under concurrent callbacks) - Include partial stdout/stderr on timeout/cancel for the LLM - UNIQUE one-active fact index; backfill inserts without inverted windows - Normalize date-only timestamps for as_of / valid_from compares - Expose knowledge_as_of via fact_history(category,key,as_of) - Drop unused search_facts; supervisor shell respects job.timeout_secs - opencode workflows: write perms, checkout@v4; ADR wording (valid-time)
|
Addressed opencode review findings:
Skipped (YAGNI): pin |
|
Both sub-agent reports verified against source. Spot-checks confirmed: cancel PR #54 Re-Review (post-fix, @ 4406413)StandardsPrior-finding verification
Hard documented-standard violation
Baseline smells (judgement)
SpecPrior-finding verification
Worst — finding 3 (spec line: "sandbox.execute_timeout_secs default 90, 0 = off"): the timeout was added to Minor gaps
Scope creep (unchanged)
Summary: Standards — 3 open (1 hard: stale CLAUDE.md test claim; worst: duplicated |
- Wire ShellBackend to sandbox.execute_timeout_secs (min with job timeout) - Kill process group on supervisor shell timeout (same as CommandTool) - Collapse normalize helpers; support ISO-8601 T timestamps - Dedupe active facts before UNIQUE index (safe re-migration) - JobOutput::failed helper; update CLAUDE.md Testing section
Re-review follow-up
Tests: knowledge 8 + shell 4 green. |
|
No PR #54 Round-3 Re-Review (post-fix, @ a07e334)Claimed-fix verification
Standards
Spec
Summary: Standards — 4 findings, worst: |
- Drain stdout/stderr concurrently while waiting (fixes large-output hang)
- Shared utils::process::{kill_child, optional_timeout}; warn on kill failure
- Dedupe active facts by max(rowid) before UNIQUE index (same-second safe)
- normalize timestamps via chrono to UTC; Fact Display; ADR gap note
Round-3 fixes
Tests: knowledge 8 + shell 5 (incl. large stdout) green. |
|
Round-4 re-review (@ Standards
Judgement-call findings:
Spec
Worst — unbounded post-wait drain await (proven by repro): Minor: Summary: Standards — 6 findings, worst: normalize fallback produces mis-orderable |
… drain_pipe helper Addresses OpenCode Round-4 findings: 1. Post-wait drain hang (🔴): wrap pipe drain in 5s timeout guard to prevent hang when daemon processes hold write end open. 2. normalize_from Z suffix (🟡): add explicit Z/UTC handling before the ISO-no-offset path so "2025-06-01T12:00Z" sorts before "2025-06-01 12:00:30" instead of after. 3. drain_pipe/drain_err collapse (🟡): replace inline per-stream pipe-reader loops with a shared generic drain_pipe<R> helper in utils::process.
|
Both axis reports verified against source ( PR #54 Round-5 Re-Review (@
|
| Claim | Verdict | Evidence |
|---|---|---|
| "shared drain_pipe helper in utils::process" | PARTIAL | Helper exists (src/utils/process.rs:20) and command_tool.rs:106/111 uses it — but shell.rs:50-72 still has the byte-identical drain_pipe/drain_err pair, untouched by this commit. |
| "explicit Z/UTC handling" in normalize | PARTIAL | "2025-06-01T12:00Z" → 12:00:00 ✓ (tested). But seconds-less no-Z "2025-06-01T12:00" still falls through knowledge.rs:503/506 to the raw ts.replace('T', " ") fallback (:509) → "2025-06-01 12:00", breaking the function's own doc contract (:479 "Canonicalize to UTC YYYY-MM-DD HH:MM:SS") — boundary compares vs 12:00:00 mis-order; untested. |
| Swallowed errors | NOT FIXED (not claimed) | h.await.unwrap_or_default() (shell.rs:138,142), Err(_) => break (shell.rs:55/67, process.rs:27); at command_tool.rs:164 is_err() only catches timeout — a drain-task panic still vanishes. Contradicts CLAUDE.md logging style. |
Dead COALESCE / untested dedupe |
NOT FIXED | memory/mod.rs:371 — valid_to is always NULL under WHERE valid_to IS NULL; still no migration test. |
| stdout cap / multibyte lossy | NOT FIXED | shell.rs:165 uncapped vs command_tool's MAX_BUFFER_CHARS; per-chunk from_utf8_lossy splits multibyte at boundaries (shell.rs:56 @8192, process.rs @4096). |
backend/mod.rs:154 0 = instant fail vs optional_timeout 0 = never |
NOT FIXED | Inconsistent semantics on the shared CLI/script path. |
New in this commit: drain_pipe_timeout (process.rs:40-51) has zero callers — the guard was open-coded at command_tool.rs:163 instead of using the helper just added. Duplicated Code: the SendMode match cascade is repeated verbatim in both result branches (command_tool.rs:191-201 vs 213-222). Primitive Obsession: normalize_from is now a 5-branch string-parse cascade; one chrono parse-with-format-list retires it.
Spec
| Round-4 finding | Status |
|---|---|
| Worst: unbounded post-wait drain hang | NOT FIXED on the supervisor path. 26a3a36 doesn't touch shell.rs; shell.rs:137-144 still awaits drain handles unguarded. Re-probe on current HEAD: ShellBackend::run("sh -c 'sleep 12 & echo hi'") did not return within 8s (grandchild holds write-end EOF). The timeout_fut is consumed by the select! on the wait branch — no guard remains. 4406413→26a3a36 claimed-fix still incomplete for this PR's own "Shell timeout" spec line. |
Same shape in command_tool.rs |
FIXED — 5s tokio::time::timeout around join! + warn! (:163-166), then try_recv flush (:168). Cost: bytes still in-flight at 5s are dropped for live-but-slow-EOF commands — acceptable vs hang, but undocumented behavior change. |
| Test gaps | OPEN — command_tool.rs has no #[cfg(test)] (no timeout-path test); shell_backend_timeout_kills_and_respects_config still asserts neither try_wait nor /proc death; memory_tools.rs has 0 tests (as_of dispatch untested). |
| python3 + 8s/10s flake in large-stdout test | UNCHANGED (shell.rs:254). |
Dedupe rowid tiebreak + idx_facts_one_active |
VERIFIED intact (memory/mod.rs:369-378); still untested. |
| CI-workflow scope creep | UNCHANGED — opencode.yml/opencode-review.yml still ride this memory PR. |
Summary: Standards — 8 findings, worst: commit ships fmt-red and clippy-red code while its message overstates two fixes (drain_pipe collapse, Z normalization). Spec — 6 findings, worst: the round-4 headline hang is fixed only in command_tool — ShellBackend::run still wedges forever on a stdout-inheriting grandchild (shell.rs:137-144), untested. run cargo fmt && cargo clippy and finish the shell.rs guard before merge.
|
Both axis reports verified against source at StandardsRound-5 claim verification for
Hard (documented-standard) issues
Judgement calls (baseline smells)
Spec
Worst — the new guard fails the scenario it was built for:
Summary: Standards — 6 findings, worst: |
- cargo fmt on command_tool, knowledge Z-suffix trim, process drain - Timeout/cancel LLM result uses labeled partial output block (capped)
PR #54 Round-6 Re-Review (@
|
| # | Finding | Status |
|---|---|---|
| S1 | drain_pipe_timeout dead helper |
NOT FIXED — src/utils/process.rs:42, zero callers repo-wide; guards still open-coded two ways (shell.rs:74 drain_with_timeout, command_tool.rs:182 timeout(5s, join!)) |
| S2 | normalize_from contract break |
PARTIAL — Z-suffix handled (knowledge.rs:491-501), but seconds-less no-Z "2025-06-01T12:00" still falls through all parses to ts.replace('T', " ") (:509) → "2025-06-01 12:00", mis-compares vs "…12:00:00" at minute boundaries; test only covers …T12:00Z |
| S3 | Swallowed errors (CLAUDE.md logging) | NOT FIXED — Ok(0) | Err(_) => break conflates I/O error with EOF (shell.rs:65, process.rs:27); let _ = tx.send (process.rs:29); let _ = edit/delete_message (command_tool.rs:223/226/258/261) while the identical call warn!s at :157 |
| S4 | SendMode cascade duplicated | NOT FIXED (command_tool.rs:216-229 vs :243-264) |
| S5 | Dead COALESCE(valid_to, valid_from) + untested dedupe |
NOT FIXED (memory/mod.rs:371) |
| S6 | Double .unwrap_or(-1) |
NOT FIXED (shell.rs:142) |
| S7 | shell path stdout uncapped + per-chunk from_utf8_lossy splits multibyte |
NOT FIXED (shell.rs:62-66; 3b3c6f0 capped only the chat-path display, not the backend capture) |
| S8 | backend/mod.rs:154 0 = instant-fail vs optional_timeout 0 = never |
NOT FIXED (latent; job default 600 masks it) |
New in 3b3c6f0: magic number 3500 hardcoded at three sites (command_tool.rs:154/204/237) — extract a const. The labeled capped partial-output block itself is a genuine improvement on the chat path.
Spec
Prior-finding verification (P1–P7):
| # | Finding | Status |
|---|---|---|
| P1 | Worst: drain guard discards already-captured output | NOT FIXED on the supervisor path — shell.rs:81-84 still returns String::new() on expiry and never aborts the handle (fd + buffer leak); sh -c 'sleep 30 & echo hi' still loses hi. Guards still awaited serially (shell.rs:152-159, 5+5s). Chat path is sound (mpsc + try_recv flush) |
| P2 | Wall-clock flake margins | PARTIAL — <5s assert vs 5s serial post-wait budget (shell.rs:253); 8s vs 10s python3 large-stdout test (:274) |
| P3 | Test gaps | NOT FIXED — memory_tools.rs and command_tool.rs have 0 #[cfg(test)] blocks (verified grep -c); as_of dispatch, CommandTool timeout path, dedupe SQL untested; timeout test asserts no child death |
| P4 | execute_timeout_secs 90 default, 0=off, min(job, sandbox) |
VERIFIED (config.rs:205, shell.rs:29-31, main.rs:244/427) |
| P5 | Cancel fix: None key + register-before-show |
VERIFIED (telegram.rs:294; command_tool.rs:91 < :100, matches ADR-0005) |
| P6 | Temporal facts: UNIQUE partial index, dedupe, auto-close, backfill, fact_history(as_of=) |
VERIFIED (memory/mod.rs:368-381, knowledge.rs:367-379, memory_tools.rs:248-252) |
| P7 | CI-workflow scope creep | UNCHANGED — opencode.yml/opencode-review.yml still ride this memory PR; @latest action remains unpinned vs CLAUDE.md CI convention (author previously marked YAGNI) |
Spec-worst restated: the PR's "Shell timeout" line still under-delivers on the supervisor path — a timed-out/hung-EOF job reports empty output, the exact failure mode 3b3c6f0's message claims to fix.
Summary: Standards — 8 open (1 partial), worst: dead drain_pipe_timeout + three divergent timeout/drain idioms with silently swallowed errors against CLAUDE.md logging conventions. Spec — 3 open + 1 partial, worst: drain_with_timeout discards captured output and leaks the drain task (shell.rs:81-84) — the round-5 headline remains unfixed, and the new commit's message overstates its scope (command_tool.rs only).







Summary
knowledge_timeline/knowledge_as_of(entity, relation, value, valid_from, valid_to); one active per pair; auto-close on new valueadd_fact,query_facts,close_fact,fact_historysandbox.execute_timeout_secsdefault 90,0= offCallbackQueryno longer chat-serialized behind in-flightexecute_command(root cause of dead Cancel)Deferred
Docs
CONTEXT.mdmemory glossaryTest plan
cargo test(incl. 6 new memory/knowledge unit tests)cargo clippy -- -D warningssleep 120→ timeout ~90sfact_history(category,key)shows archive