feat(memory): eager working-memory refresh via Stop-hook claude -p worker#239
Conversation
Pre-existing uncommitted changes to decisions, pitfalls, feature knowledge bases, and gitignore files accumulated since last PR merge.
…rker Revives and modernises the pre-#221 background-memory-update design: the dream-capture Stop hook now spawns a detached `claude -p --model haiku` worker directly instead of writing a memory.json Dream marker, giving eager refresh on every session stop rather than deferring to the next SessionStart Dream agent cycle. Key changes: - scripts/hooks/background-memory-update: new worker script; acquires a 300 s stale-break lock, drains .pending-turns.jsonl, invokes haiku via stdin (never argv), writes WORKING-MEMORY.md with a git-reconciliation stamp on line 1 (`<!-- memory-head: <sha> branch: <name> -->`), touches .last-refresh-ok on success, leaves .processing on failure/timeout. Watchdog kill at 120 s prevents zombies. DEVFLOW_BG_UPDATER=1 recursion guard prevents re-entry from haiku's own Stop hook. - scripts/hooks/dream-capture: replaced marker-write block with worker spawn via nohup+disown; throttled by .working-memory-last-trigger. - scripts/hooks/session-start-context: removed raw-queue bridge (Step 3) that added memory to _DREAM_TASKS; no memory spawn from here. - scripts/hooks/dream-collect-tasks: memory) case is now unconditional sweep (deletes any stale memory.* markers, same as learning.*); removed _dbsd_mem from dream_build_spawn_directive. - scripts/hooks/session-start-memory: complete rewrite; parses git-stamp from line 1, computes drift with git rev-list --count, emits 3-state header (A=in-sync, B=drifted N commits, C=refresh failing), branch- mismatch banner when stamp branch != current branch; no raw-turns dump. - shared/skills/dream-memory/SKILL.md: deleted (no longer a Dream task). - shared/agents/dream.md: removed memory from task list and skill refs. - src/cli/plugins.ts: removed dream-memory from devflow-core-skills skills array; added prefixed devflow:dream-memory to LEGACY_SKILL_NAMES. - src/cli/utils/migrations.ts: added purge-stale-memory-markers-v1 per-project migration to remove legacy memory.* Dream markers. - CLAUDE.md: updated Working Memory description, hook list, runtime files. - All tests pass (1490).
…: clarify memory param is API-compat only; memory markers are always swept unconditionally (no longer a Dream subagent task) - session-start-context: remove stale step-numbering comment referencing the removed raw-queue bridge; plain emit-directive comment is sufficient - session-start-memory: remove duplicate source get-mtime line (dedup) - plugins.ts: update Dream agent placement comment to drop memory/learning and reflect current decisions/knowledge/curation subsystems only Co-Authored-By: Claude <noreply@anthropic.com>
…-hook worker design Replace all references to the old dream-marker-based memory pipeline with the shipped background-memory-update worker design: docs/working-memory.md: - Stop-hook row: was "writes memory.json dream marker" — now correctly describes spawning the detached background-memory-update worker after the 120s throttle; no dream marker is written for memory - Add background-memory-update worker row (detached claude -p haiku process) - SessionStart row: was "spawns Dream agent that rewrites WORKING-MEMORY.md" — now correctly states session-start-memory only reads/injects the already-written file with 3-state git-reconciled header (A/B/C) - session-start-context row: clarified it handles DREAM MAINTENANCE directive for decisions/knowledge/curation only; memory is NOT a Dream task - File Structure: remove dream/memory.{session}.json (no longer exists), add .working-memory-last-trigger and .last-refresh-ok entries, add line-1 stamp annotation to WORKING-MEMORY.md entry - Add note that dream/memory.* markers no longer exist for memory - Working Memory Sections table: attribute to background-memory-update worker, not "the Dream agent" docs/reference/file-organization.md (Dream Hooks section): - dream-capture row: was "writes .devflow/dream/memory.json marker" — now correctly says spawns background-memory-update detached worker via nohup - Add background-memory-update row to the hooks table - session-start-memory row: was "warns if >1h stale" — now correctly describes 3-state injection (A/B/C) and that it only reads/injects - session-start-context row: note memory markers swept unconditionally - Flow narrative: replace Dream-subagent memory path with detached worker path; explicitly state memory is NOT a Dream task Co-Authored-By: Claude <noreply@anthropic.com>
…te to file-organization source tree
|
🔴 BLOCKING: This PR adds Fix: Remove Suggested test: assert that Claude Code | Regression Reviewer |
|
HIGH (82%) — Code injection via project path in node-fallback orphan check.
Fix: Pass the path via _HAS_ASSISTANT=$(node -e '
const lines = require("fs").readFileSync(process.argv[1],"utf8").split("\n");
for (const l of lines) {
try { const o = JSON.parse(l); if (o.role==="assistant") { process.stdout.write("assistant"); break; } } catch {}
}
' "$QUEUE_FILE" 2>/dev/null || echo "")This matches the pattern already used at line 198 and the safe jq branch. Claude Code | Security Reviewer |
|
MEDIUM (80%) — Untrusted memory-file content flows into
git merge-base --is-ancestor "$STAMP_SHA" "$HEAD"
git rev-list --count "${STAMP_SHA}..${HEAD}"
git log --oneline "${STAMP_SHA}..${HEAD}"A stamp value beginning with Fix: Validate the SHA shape before use: case "$STAMP_SHA" in
*[!0-9a-fA-F]* | "" ) STAMP_SHA="" ;; # only hex; clears non-SHA values
esacClaude Code | Security Reviewer |
|
MEDIUM (92%) — Dead parameter
Fix: Drop the parameter from the function signature and call site, or if positional stability is desired, delete the pointless Claude Code | Complexity Reviewer |
|
MEDIUM (82%) — Watchdog kills only the direct PID with SIGTERM; hung
Fix: Kill the process group and escalate: ( sleep 120 && kill -TERM -$PGID 2>/dev/null; sleep 5 && kill -KILL -$PGID 2>/dev/null ) &Or at minimum: ( sleep 120 && kill "$CLAUDE_PID" 2>/dev/null; sleep 5 && kill -9 "$CLAUDE_PID" 2>/dev/null ) &Claude Code | Reliability Reviewer |
|
MEDIUM (85% consistency, 92% documentation) — This PR re-attributed the Working Memory Sections table from "the Dream agent" to "the CLAUDE.md:47 was correctly updated to the five-section list, so Fix: Delete the | `## Decisions` | Architectural and design decisions made this session |
| `## Context` | Repository state, build status, test results |
| `## Session Log` | Timestamped log of significant actions |Claude Code | Consistency Reviewer |
Summary: Code Review FindingsPR: #239 | Branch: feat/eager-memory-refresh -> main Inline Comments Posted (≥80% confidence)
Lower-Confidence Suggestions (60-79%)The following ≥60% suggestions are consolidated here for awareness but do not block merge:
Merge RecommendationCHANGES REQUESTED The 1 CRITICAL blocker (worker deletion) must be fixed before merge. Once Claude Code | Synthesizer |
The worker was incorrectly listed in the legacy-deletion array, causing installViaFileCopy to install it and the cleanup loop to immediately delete it on every devflow init. Memory refresh was dead-on-arrival for all installed users outside the source tree. Adds S12 regression test in eager-memory-refresh.test.ts asserting that the worker is absent from LEGACY_HOOK_FILES and executable in the source hooks directory. avoids PF-009 (new file wrongly landing in a deletion list — inverse of stale-reference pitfall) Co-Authored-By: Claude <noreply@anthropic.com>
…bility Security (command injection): - Pass QUEUE_FILE via process.argv[1] instead of string-interpolating into node -e source. A repo path containing a single quote could break out of the JS string literal into arbitrary code execution in the detached worker. applies ADR-008 (plumbing only; no logic change) Reliability (watchdog escalation): - Upgrade watchdog from single SIGTERM → process-group SIGTERM + 5s grace + SIGKILL. Uses ps -o pgid= to kill the entire group so children of claude -p cannot escape the 120s bound. Falls back to per-PID kill on PGID lookup failure. Consistency (_JSON_AVAILABLE guard): - Add _JSON_AVAILABLE=true guard to the orphan-only check so the block is skipped (conservative: allow the run) when neither jq nor node is present, matching sibling hook conventions. Also split the EXTRACTED extraction block into explicit jq / elif node / else branches for the same reason. Consistency (logging): - Source shared hook-log-init instead of hand-rolling log() and rotate_log(). hook-log-init is safe to source standalone (does not pull in hook-bootstrap). avoids PF-007 Reliability (retry ceiling comment): - Document explicitly that persistent failure is rate-bounded (not count-bounded) and that session-start-memory State C is the user-visible surfacing mechanism. applies ADR-008 Co-Authored-By: Claude <noreply@anthropic.com>
Under set+m (the default in non-interactive scripts), a backgrounded child stays in the shell's own process group. The previous watchdog used ps -o pgid= to get the group and then kill -TERM -PGID, which sent SIGTERM to the entire group -- including the worker itself blocked on wait CLAUDE_PID -- so the clean failure path never executed. Fix (Option A): wrap only the claude spawn with set -m / set +m. Under set -m, bash makes the background job its own process-group leader (PGID == CLAUDE_PID) on bash 3.2+ and Linux. The watchdog now does kill -TERM -CLAUDE_PID which targets only claude's subtree; the worker is not in that group (set +m restored immediately after capture). The ps-based PGID lookup is removed. Also add DEVFLOW_BG_WATCHDOG_SECS env override (default 120) so tests can use a 2s timeout instead of waiting 120s. Add a new behavioral test AC-F3/watchdog-behavioral that verifies: claude is killed, worker exits 0, .last-refresh-ok is NOT touched, .processing is retained for dream-recover. Co-Authored-By: Claude <noreply@anthropic.com>
…n + log-dir reuse
- Validate STAMP_SHA from WORKING-MEMORY.md line 1 against a hex
object-id shape ([0-9a-f]{7,40}) immediately after parsing, before
any git call. Values beginning with '-' or containing non-hex chars
are rejected: STAMP_SHA and STAMP_BRANCH are cleared, falling
through to the no-stamp / State A path. Preserves 3-state A/B/C
behavior exactly for valid stamps. Avoids PF-007 (edit source, not
installed copy). Applies engineering boundary-validation rule.
- Replace inline log-dir slug recompute in the State C hint message
with the LOG_DIR variable already set by hook-log-init, matching
the convention used by all sibling hooks.
- Add two malformed-stamp test cases to eager-memory-refresh.test.ts
(starts-with-dash, contains-dotdot) confirming the rejection falls
through to "synced @ unknown" without leaking the bad value.
Co-Authored-By: Claude <noreply@anthropic.com>
…ks Memory is not a Dream task (ADR-016); mem_enabled was dead plumbing from the removed dream-marker memory path. Removed end-to-end (PF-009): - dream-collect-tasks: drop MEMORY_ENABLED positional arg; renumber dec_enabled to $2, know_enabled to $3; update header comment - session-start-context: stop computing _SC2_MEM_EN (saves a json_field_file subprocess + sentinel check each SessionStart); pass 3-arg call to dream_collect_tasks - tests/shell-hooks.test.ts: remove memEnabled from runCollectTasks opts type, drop 4th arg from bash script, update all call sites The memory case in dream-collect-tasks remains unconditional. Co-Authored-By: Claude <noreply@anthropic.com>
…ine, stdin safety, and migration rethrow S13: D56c crash-recovery merge — pre-seed .pending-turns.processing AND .pending-turns.jsonl with distinct sentinel content; assert both batches appear in claude's captured stdin (prior turns not dropped). Second case verifies the 200-line overflow cap silently truncates at 100 lines. S14: .last-refresh-ok baseline discipline — capture Date.now() before the run and assert mtime is >= baseline (success path); on claude-exit-1 pre-seed a stale file and assert mtime is UNCHANGED after the failed run, proving the worker did not touch it. S15: stdin/argv safety — fake claude shim records argv to argv-captured.txt and stdin to stdin-captured.txt; assert SENTINEL turn content appears in stdin and is absent from argv (applies ADR-008 behavior-over-implementation). migrations: two rethrow tests for purge-stale-memory-markers-v1 — mock unlink and readdir to throw EACCES; assert the migration propagates rather than swallowing (avoids PF-004: error contract must be right the first time). Co-Authored-By: Claude <noreply@anthropic.com>
…-update Three clarity improvements flagged by complexity review, no behavior change: - PROC_OVERFLOW_CAP=200 / PROC_OVERFLOW_TARGET=100: the processing-file truncation policy values are now named, making the 200-line threshold and 100-line trim target self-documenting rather than open-coded. - MEMORY_READ_LIMIT=65536: names the head -c byte cap passed to claude -p so the prompt-size policy is visible next to the read call. - WATCHDOG_KILL_GRACE_SECS=5: names the SIGTERM→SIGKILL grace delay, consistent with the existing WATCHDOG_SECS / STALE_THRESHOLD pattern. All 43 eager-memory-refresh and 155 shell-hooks tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
…ale `## Modified Files` row (worker emits 5 sections: Now/Progress/Decisions/Context/Session Log) - hooks KB: correct dream_collect_tasks 4-arg -> 3-arg signature (memory arg removed when memory left the Dream pipeline) - cli-rules KB + index.json: background Dream knowledge-agent refresh (four -> three active per-task Dream skills; dream-memory removed) - decisions.md ADR-016: amendment noting memory is no longer a Dream task (moved to background-memory-update worker); haiku-for-memory superseded Co-Authored-By: Claude <noreply@anthropic.com>
dean0x
left a comment
There was a problem hiding this comment.
Cycle 2 Code Review Summary
Status: ✅ All high-confidence in-diff findings already posted as inline comments (cycle 1).
Inline Comments (≥80% confidence, anchored to changed lines)
The following findings have inline comments at their source locations:
- CLAUDE.md:189 — Model Strategy still lists "haiku for memory" (HIGH, 95%)
- tests/eager-memory-refresh.test.ts:580 — Lock test vacuous when
timeoutabsent (HIGH, 90%) - scripts/hooks/session-start-memory:129 — Redundant double range-walk (MEDIUM, 88%)
- scripts/hooks/background-memory-update:89 — Lock-acquire timeout lacks rationale (MEDIUM, 82%)
- scripts/hooks/session-start-memory:57 — 144-line mega-block (MEDIUM, 85%)
- scripts/hooks/background-memory-update:208 — Turn-builder loop lacks invariant comment (MEDIUM, 80%)
Pre-existing Issues (Not Blocking)
Reliability & Architecture Carry-overs (No Fix Applied in Cycle 1)
Two MEDIUM findings from cycle 1 remain unaddressed and are still present in the current code:
| Finding | Confidence | Impact | Fix Recommendation |
|---|---|---|---|
break_stale_lock non-atomic check-then-rmdir (scripts/hooks/background-memory-update:76-86) |
80% | Lock-break does not verify owner is dead; relies on implicit invariant STALE_THRESHOLD (300s) > watchdog total in-flight time (~125s). If watchdog timeout increases past 300s, two workers could write concurrently (outcome: redundant idempotent write, not corruption, but mutual exclusion is undocumented). |
Stamp lock with owner PID at acquire and only break when stale AND ! kill -0 <pid> (owner dead). Or add assertion-comment documenting the invariant. |
dream-recover moves .pending-turns.processing without lock (scripts/hooks/dream-recover:148-170 vs. background-memory-update:141-156) |
80% | Recovery fires at SessionStart without acquiring .working-memory.lock, racing the worker's leftover-merge. Narrow impact (the recovery only fires when .processing is >300s old AND no .jsonl exists, by which time any live worker has been watchdog-killed). Still carries the same implicit invariant as above. |
Add inline comment in dream-recover documenting that lock-free recovery is safe-by-construction because 300s > watchdog total (~125s). |
Both carry the same root cause: safety depends on an implicit invariant that should be documented (or enforced with PID checks).
Pre-existing Documentation Drift
- hooks/KNOWLEDGE.md:241 (Confidence: 90%) — Stale
dream-fallbackclaim asserts a transitional fallback to.devflow/sidecar/config.jsonthat has been removed from the hooks source. This is a PF-009 instance (rename/removal leaves stragglers in lower-visibility surfaces). Suitable for the next knowledge base refresh.
Pre-existing TypeScript Issue (Harmless)
- src/cli/plugins.ts:531-533 (Confidence: 82%) —
LEGACY_SKILLS_V2Xlists still-active bare skill names (dream-decisions,dream-knowledge,dream-curation) alongside genuinely-removed ones. This is harmless today because active skills install under namespaced paths, so the post-install cleanup targets non-existent bare-name directories. However, it is a latent footgun: if a future install ever wrote bare-name skill dirs, the cleanup would delete freshly installed skills. The PR's new entrydevflow:dream-memory(namespaced, genuinely removed) is correct.
Lower-Confidence Suggestions (60-79%)
Consolidated from all reviewers; not actionable as blocking but worth noting:
Architecture & Complexity
background-memory-updateapproaches god-script size (406 lines, 6+ responsibilities). The lock + watchdog subsystems are self-contained and could be extracted into sourced helpers (matchingdream-lock/hook-log-initpattern) for independent testability. Assess if future additions warrant refactoring.- State-C "9999999 sentinel" for "never" is slightly opaque; a
OK_FILE_PRESENTboolean would read more directly.
Performance
background-memory-updatere-deriveswc -lline counts at 3 sites (:151,164,176) against the same file; a single read after the claim would remove one subprocess.- Two passes over the processing file (orphan-check and extraction) on ≤20 capped lines; unification is premature optimization but worth tracking.
Testing
- S5 timing flake residue (Confidence: 65%) — Even when
timeoutexists, the test burns ~2s of wall-clock per run relying ontimeout 2to race the worker's 90sacquire_lockwait. Recommend addingtimeout: 15000torunWorkerexecSync options to bound the suite and convert intermittent hangs into clear, reproducible failures. - S2 git-state mutation tests assert on substrings that overlap user content; would benefit from asserting on structural markers (
commit(s) agocount) rather than log-message text. - Structural grep assertions (S4 watchdog, S5 lock, S8 stdin/env/comment) duplicate existing behavioral tests (AC-F3, S15) and break on harmless refactors. Consolidate so one structural assertion per security invariant remains.
Consistency
- Throttle key semantics changed from "last successful write" to "last spawn attempt" (
dream-capture:151-164). Likely intentional (prevents spawn storms), but worth a one-line comment documenting the deliberate shift. .last-refresh-okis a separate liveness signal thatsession-start-memoryreads but the worker is the sole writer — couples the SessionStart hook to worker-private sentinel. If a fourth cross-hook file contract is added, the implicit file-protocol should be documented in one place (e.g., the hooks KB).
Decisions Citations
The PR applies the following decisions:
- ADR-008 (LLM-vs-plumbing) — worker confines itself to plumbing; content is LLM-authored by
claude -p - ADR-016 (Dream split into per-task skills) — memory removed from Dream task set; correctly contracted
- PF-009 (subsystem rename leaves stale refs) — end-to-end removal verified; one KB doc-drift item pre-existing
- PF-004 (migration idempotency) — migration correctly ENOENT-idempotent + non-ENOENT rethrow
- PF-006 (worker parse Stop-hook JSON) — worker receives CWD via argv, does not parse JSON
Recommendation
APPROVED_WITH_CONDITIONS
The PR is production-ready. Conditions for merge readiness:
- Fix CLAUDE.md:189 — Update Model Strategy to remove "haiku for memory" reference (inline comment has suggestion)
- Fix tests/eager-memory-refresh.test.ts:580 — Add precondition guard or drop
timeoutdependency (inline comment has suggestion) - Consider addressing carry-over issues — The two cycle-1 MEDIUMs (
break_stale_lock,dream-recoverrace) remain and should be addressed via documented invariants or PID-liveness checks before merge. Both share a single root cause; one unified fix is preferable.
All other findings are quality-of-life improvements, pre-existing issues, or lower-confidence suggestions suitable for backlog or future refactors.
Claude Code | Git Agent (comment-pr operation)
Cycle 2 Code Review SummaryStatus: ✅ All high-confidence in-diff findings already posted as inline comments (from cycle 1 review). Inline Comments (≥80% confidence, anchored to changed lines)The following findings have inline comments at their source locations:
Pre-Existing Issues (Not Blocking)Reliability Carry-overs from Cycle 1 (No Fix Applied) Two MEDIUM findings remain unaddressed and still present:
Both share a root cause (implicit invariant should be documented); one unified fix preferred. Documentation Drift (Pre-existing)
TypeScript (Pre-existing, Harmless)
Lower-Confidence Suggestions (60-79%)Architecture & Complexity
Performance
Testing
Consistency
Decisions Applied✅ ADR-008 (LLM-vs-plumbing) — worker is plumbing; content LLM-authored RecommendationAPPROVED_WITH_CONDITIONS This PR is production-ready. Conditions for merge:
All other findings are quality-of-life improvements or pre-existing issues suitable for backlog/future refactors. Claude Code | Git Agent — comment-pr operation, cycle 2 |
Auto-detected pitfalls materialized by the decisions Dream agent during the cycle-2 review of PR #239: - PF-010: init-time LEGACY_HOOK_FILES listed a still-active hook -> devflow init deleted the worker it had just installed (ship-blocker, now guarded by test) - PF-011: watchdog process-group SIGKILL self-killed the worker (shared group); fixed via set -m group isolation + behavioral test Co-Authored-By: Claude <noreply@anthropic.com>
… not Dream Line 189 listed "haiku for memory" as a Dream per-task override, contradicting the code and the PR's own ADR-016 amendment 40 lines above. Memory is no longer a Dream task — WORKING-MEMORY.md is refreshed by the detached background-memory-update worker (claude -p --model haiku). Resolves cycle-2 HIGH doc-drift (avoids PF-009). User-approved markdown edit. Co-Authored-By: Claude <noreply@anthropic.com>
… in background-memory-update - acquire_lock 90s timeout: rationale that it undercuts WATCHDOG_SECS so a waiter gives up before the holder's watchdog fires, preserving the queue - STALE_THRESHOLD 300s: INVARIANT comment — 300s > watchdog total (120+5+margin); future changes to either bound must re-verify - header: correct recovery ownership — worker is PRIMARY owner (merges leftover .processing under the lock); dream_recover_stale is cold-path fallback - turn-stanza builder: FLUSH INVARIANT comment (flush on next user, assistant arrival, or EOF) Applies ADR-008. Avoids PF-007. Co-Authored-By: Claude <noreply@anthropic.com>
… in session-start-memory - perf: single git log --oneline replaces the rev-list --count + git log double rev-walk over STAMP_SHA..HEAD; count via grep -c ., display via head -10 on the same capture; output byte-identical - complexity: extract parse_and_validate_stamp (stamp parse + hex-validation gate) and detect_refresh_failing (State-C) to flatten the 144-line mega-block; hex gate preserved before any git use; internal temporaries scoped with local Avoids PF-007. Co-Authored-By: Claude <noreply@anthropic.com>
Add inline comment to dream_recover_stale's .pending-turns recovery block: background-memory-update is the PRIMARY recovery owner (under .working-memory.lock); this path is the cold-path fallback. Lock-free recovery is safe-by-construction because STALE_THRESHOLD (300s) strictly exceeds watchdog total (~125s). INVARIANT: raising STALE_THRESHOLD to >= watchdog total would silently open a race. Applies ADR-008. Avoids PF-007. Co-Authored-By: Claude <noreply@anthropic.com>
- S5: drop GNU timeout dependency (absent on macOS, made the test pass vacuously); use Node execSync({ timeout: 3000 }) + positive log evidence ("Starting (CWD=") to prove the worker actually ran
- S8: drop redundant structural greps with behavioral twins (watchdog AC-F3, stdin S15); replace the comment-grep with a behavioral sentinel log-leak check
- S16 (new): queue-claim lost-race -> SKIP path (read-only .processing dir); asserts log msg, queue preserved, exit 0
- S17 (new): jq+node absent degraded path via symlink farm; asserts conservative no-truncation path
42/42 pass on macOS without GNU timeout. Applies ADR-014.
Co-Authored-By: Claude <noreply@anthropic.com>
…d skill overlap - plugins.ts: block comment on LEGACY_SKILLS_V2X bare dream-* entries — they target pre-namespace V2.x install dirs (harmless no-op on namespaced installs; required for upgraders). Prevents a future reader 'fixing' it by deleting them. - plugins.test.ts: add LEGACY_SKILL_NAMES consistency guard (mirrors LEGACY_AGENT_NAMES) asserting no namespaced legacy entry collides with an active skill install path; scoped to namespaced entries (bare entries double as active skill names by design). Comment avoids a literal devflow: skill ref to satisfy skill-references scanner. Applies ADR-016. Avoids PF-009. Co-Authored-By: Claude <noreply@anthropic.com>
…document recovery ownership
Audit of the hooks KNOWLEDGE.md against current code (prompted by the cycle-2 review's pre-existing finding at line 241):
- Dream Config section: removed the false claim that hooks read .devflow/sidecar/config.json at runtime via a '# dream-fallback' path — grep finds zero such references. Reality (src/cli/utils/dream-config.ts): ADR-001 clean break, dream/config.json is the sole source of truth; legacy sidecar configs are migrated once at init by rename-sidecar-to-dream-v1, not read at runtime. Heading renamed Dream Config & Legacy Fallback -> Dream Config.
- Queue: Memory Channel: documented the two lock-separated recovery paths clarified in this PR — background-memory-update is the PRIMARY recovery owner (merges leftover .processing on next spawn under .working-memory.lock); dream_recover_stale is the cold-path fallback (300s gate, safe because it exceeds the ~125s watchdog total).
- index.json: bumped hooks lastUpdated (KB re-verified against current code; staleness check now clean).
Audit confirmed all other KB claims accurate: 1800s/300s recovery thresholds, 3-retry/.failed/24h cleanup, 80-turn/1200-char transcript caps, 30 dialog-pair input cap, 200->100 queue overflow, 600s State-C threshold + header text, DreamConfig {memory,decisions,knowledge}, eval-module list, DEVFLOW_BG_LEARNER guard still present in code.
Avoids PF-009 (stale references in lower-visibility surfaces). Applies ADR-001, ADR-008.
Co-Authored-By: Claude <noreply@anthropic.com>
|
File: scripts/hooks/session-start-memory Issue: Rev-walk dedup mis-derives COMMITS on empty-log fallbackThe count derivation on line 173: COMMITS=$(printf '%s\n' "$_DRIFT_LOG_FULL" | grep -c . 2>/dev/null || echo "0")When The subsequent guard Verification
FixDrop the count's _DRIFT_LOG_FULL=$(git log --oneline "${STAMP_SHA}${HEAD}" 2>/dev/null || echo "")
COMMITS=$(printf '%s\n' "$_DRIFT_LOG_FULL" | grep -c . 2>/dev/null)
COMMITS=${COMMITS:-0}
_DRIFT_LOG=$(printf '%s\n' "$_DRIFT_LOG_FULL" | head -10)This ensures Review by Claude Code | Devflow code-review cycle 3 |
|
File: tests/eager-memory-refresh.test.ts Issue: S17 does not exercise the degraded JSON path it claims to testS17's header comment documents testing the
However, reproductions of the test's exact harness show the worker exits much earlier at the claude-binary gate: The Impact
FixAdd a fake // Put claude in the farm so the worker proceeds past the binary gate
const farmClaude = path.join(farmDir, 'claude');
fs.writeFileSync(farmClaude, '#!/bin/bash\nexit 0\n');
fs.chmodSync(farmClaude, 0o755);
// Then assert the documented observable:
expect(logContent).toContain('No parseable turns — skipping'); // proves degraded branch ranThis ensures S17 actually covers the path it documents, not just a coincidental early exit. Review by Claude Code | Devflow code-review cycle 3 |
PR Review Summary — Cycle 3Base: main (59db82e) High-Confidence Findings (≥80%)2 blocking issues posted as inline comments:
Lower-Confidence Suggestions (60–79%)Testing Report — Should Fix
Performance Report — Minor Clarity Nit
Genuine Fixes Verified (Cycle 2 → 3)Cross-cycle analysis confirms prior cycle-2 work:
FALSE_POSITIVE from cycle 2 (correctly not re-raised):
Scores & Recommendation
Status: CHANGES_REQUESTED The rev-walk dedup is a real, correct optimization (one walk instead of two). The blocking issues are correctness (session-start-memory) and test vacuity (S17), both trivial fixes. Recommend fixing in place before merge — none require architectural changes. Cycle Convergence: This is cycle 3. FP ratio (false positives / total findings) is still reasonable; no 70%+ convergence warning triggered. Review by Claude Code | Devflow code-review |
…und-memory-update Enforce the lock-free safety invariant (STALE_THRESHOLD > WATCHDOG_SECS + WATCHDOG_KILL_GRACE_SECS) as a NASA/JPL Rule 5 runtime assertion rather than relying on prose documentation alone. Placed before the claude -p spawn (after all three constants are assigned and after the DEVFLOW_BG_WATCHDOG_SECS override is applied) so a misconfigured override fails loudly before launching the subprocess. Statically true today: 300 > 120+5=125 (175s margin). Test harness uses DEVFLOW_BG_WATCHDOG_SECS=2, giving 293s margin — unaffected. avoids PF-011
…ry State-B fallback
The previous derivation used `grep -c . || echo "0"`. Under set -e, grep -c exits 1 on
zero matches (POSIX); the || fired correctly to prevent abort, but grep had already emitted
"0" to stdout — so the result was the two-line string "0\n0". Downstream, [ "$COMMITS" -le 0 ]
would error "integer expression expected" (exit 2 under set -e) or misroute the empty-log
transient-failure path into State B, showing a spurious reconcile banner.
Fix: replace || echo "0" with || true (prevents set -e abort cleanly) and add
COMMITS=\${COMMITS:-0} as parameter-expansion default for the case where grep emits
nothing. Both paths now produce a guaranteed single-line integer.
applies ADR-008 (plumbing-only fix, no LLM judgment logic touched)
avoids PF-007 (editing source scripts/hooks/, not installed copy)
Co-Authored-By: Claude <noreply@anthropic.com>
… assertions S17 vacuous-test fix: the worker's `command -v claude` gate (line 69-73) runs BEFORE any JSON-parse logic, so the old harness (shimDir=/nonexistent-shim) caused an early SKIP before ever reaching the _JSON_AVAILABLE=false branch. Fix: create a real fake `claude` shim in a shimDir and prepend it to the no-jq/no-node PATH farm so the binary gate passes. Seed queue with user+assistant turns (so the orphan guard, which is skipped in degraded mode, would not have exited early anyway). Tighten assertion from generic 'Starting (CWD=' to the path-specific 'No parseable turns' log message, confirming the degraded branch is actually reached. State-B count assertion: strengthen 'commit(s) ago' to '2 commit(s) ago' to catch off-by-one bugs in the rev-walk dedup (the source was fixed in b3b5d6c to guarantee a single-line integer via `grep -c . || true; COMMITS=${COMMITS:-0}`). S5 dead code: remove the vacuous `void workerRanAtAll` variable and its declaration — the log assertion is the canonical positive evidence and the variable was only retained to suppress an unused-variable lint warning. Empty-log fallback coverage: skipped — simulating a transient git-log failure cleanly/deterministically is not feasible without brittle mocking, per the reviewer's guidance.
…n S17 macOS BSD grep+sed: degraded extraction yields EXTRACTED="" -> TURN_COUNT=0 -> logs "No parseable turns" -> exit 0. Linux GNU grep+sed: degraded extraction may extract partial data -> TURN_COUNT>0 -> invokes no-op fake claude -> verification fails -> logs "FAIL: verification failed -- leaving .processing for recovery" -> exit 0. Both exit paths are equally safe: no WORKING-MEMORY.md written, worker exits 0. The old assertion pinned the macOS-only log string; rewrite to assert the platform-independent contract: - binary gate was passed (not a SKIP exit) - no memory write - exit 0 - log contains at least one of the two known conservative-exit markers Co-Authored-By: Claude <noreply@anthropic.com>
Summary
claude -p --model haikuworker spawned directly from thedream-captureStop hook, giving eager refresh at the end of every session rather than deferring to the next SessionStart Dream cyclesession-start-memorywith 3-state git-reconciled injection: A (in-sync), B (drifted N commits with reconcile banner), C (refresh failing), plus a branch-mismatch warning when the stamp branch differs from the current branchdream-memoryskill entirely — memory is no longer a Dream subagent taskChanges
New file:
scripts/hooks/background-memory-update— detached worker: 300s stale-break lock, drains.pending-turns.jsonl, invokesclaude -p --model haikuvia stdin (never argv), writesWORKING-MEMORY.mdwith a<!-- memory-head: <sha> branch: <name> -->stamp on line 1, 120s watchdog,DEVFLOW_BG_UPDATER=1recursion guardModified hooks:
dream-capture— replaced memory marker-write with worker spawn vianohup+disown; throttled by.working-memory-last-triggersession-start-context— removed raw-queue bridge (Step 3) that addedmemoryto_DREAM_TASKSdream-collect-tasks—memory)case is now unconditional sweep (stale marker cleanup, same treatment aslearning.*); removed_dbsd_memfromdream_build_spawn_directivesession-start-memory— complete rewrite: parses git stamp from line 1, computes drift, emits 3-state header, no raw-turns dumpRemovals / migrations:
shared/skills/dream-memory/SKILL.mddeletedshared/agents/dream.md— memory removed from task list and skill refssrc/cli/plugins.ts—dream-memoryremoved fromdevflow-core-skillsskills array;devflow:dream-memoryadded toLEGACY_SKILL_NAMESsrc/cli/utils/migrations.ts—purge-stale-memory-markers-v1per-project migration removes legacymemory.*Dream markersDocs:
CLAUDE.md— Working Memory paragraph, hook list, runtime file inventory updatedReviewer Focus Areas
scripts/hooks/background-memory-update: lock acquisition (300s stale-break),mv-claim atomicity, watchdog cancel afterwait,.last-refresh-okonly touched on success, recursion guardscripts/hooks/session-start-memory: git stamp parsing, 3-state logic, branch-mismatch banner pathscripts/hooks/dream-capture: throttle file touch BEFORE spawn (prevent double-spawn race),nohup/disowndetachDEVFLOW_BG_UPDATER=1recursion guard