fix: learning system empty-field validation + debug log relocation#161
fix: learning system empty-field validation + debug log relocation#161
Conversation
Fixes the learning system's background-learning script where Sonnet returns observations with empty fields, causing a self-matching feedback loop. Adds field validation, transcript quality gate, EXISTING_OBS filtering, debug mode, and relocates debug logs to user scope (~/.devflow/logs/). - Validate observation fields (rule, pattern, workflow) before storage - Skip transcripts with <3 observations from Sonnet (quality gate) - Filter EXISTING_OBS from considerations when generating new - Add DEBUG env var support for detailed logging - Move debug logs from project to user scope for persistent troubleshooting - Update CLAUDE.md and README.md with learning debug guidance Co-Authored-By: Claude <noreply@anthropic.com>
src/cli/utils/post-install.ts
Outdated
| const valid = lines.filter(line => { | ||
| try { | ||
| const obj = JSON.parse(line) as Record<string, unknown>; | ||
| return obj.id && obj.type && obj.pattern; |
There was a problem hiding this comment.
Validation mismatch: Weaker check in migration purge
The migration auto-purge uses obj.id && obj.type && obj.pattern (JS truthiness check), but isLearningObservation() is much stricter — it validates:
confidenceis a numberobservationsis a numberstatusis one of three enum valuesevidenceis an arraydetailsis a string- Fields are non-empty strings (not just truthy)
Result: Entries like {"id":"x","type":"invalid","pattern":"y"} survive migration but get rejected by --purge, creating inconsistent cleanup.
Fix: Import and reuse parseLearningLog from learn.ts instead of the inline filter.
Blocking issue (HIGH: 90% confidence) — flagged by architecture, consistency, complexity, and TypeScript reviewers.
|
|
||
| // Old files should be gone | ||
| await expect(fs.access(path.join(memoryDir, '.learning-update.log'))).rejects.toThrow(); | ||
| await expect(fs.access(path.join(memoryDir, '.working-memory-update.log'))).rejects.toThrow(); |
There was a problem hiding this comment.
Test writes to real ~/.devflow/ directory instead of sandbox
This test calls migrateMemoryFiles() which internally resolves getDevFlowDirectory() to the actual ~/.devflow/ path. While cleanup runs after the test, this creates risk:
- Test crashes before cleanup → orphaned files in user home
- CI environments may have different permissions/constraints
- Parallel test runs could collide on the same path
Fix: Inject the devflow directory path or refactor migrateMemoryFiles to accept an optional devflowDir parameter for testability.
Blocking issue (HIGH: 90% confidence) — flagged by tests reviewer.
scripts/hooks/background-learning
Outdated
| LOG_FILE="$CWD/.memory/.learning-update.log" | ||
| _PROJECT_SLUG=$(echo "$CWD" | sed 's|^/||' | tr '/' '-') | ||
| _LOG_DIR="$HOME/.devflow/logs/$_PROJECT_SLUG" | ||
| mkdir -p "$_LOG_DIR" |
There was a problem hiding this comment.
Log files created with world-readable permissions
The mkdir -p call creates directories with default umask (typically 022), making the log files world-readable (mode 644) on multi-user systems. These logs contain session IDs, timestamps, and (in debug mode) excerpts of session content.
Fix: Add restrictive permissions after directory creation:
mkdir -p "$_LOG_DIR"
chmod 700 "$_LOG_DIR"Apply across all four hook scripts (background-learning, background-memory-update, stop-update-learning, stop-update-memory).
Should-fix issue (MEDIUM: 82-85% confidence) — flagged by security reviewer.
| @@ -27,8 +30,15 @@ log() { | |||
| } | |||
|
|
|||
| rotate_log() { | |||
There was a problem hiding this comment.
rotate_log inconsistency between background-learning and background-memory-update
background-learning now has debug-aware log rotation (500/250 lines when DEBUG=true, 100/50 otherwise), but background-memory-update still uses hardcoded 100/50. Both scripts write to the same ~/.devflow/logs/ directory, so users enabling debug mode would expect consistent retention behavior.
Fix: Either apply the same debug-aware rotation to both scripts, or extract rotate_log into the shared json-parse source file. At minimum, add the early-return guard to background-memory-update:
rotate_log() {
if [ ! -f "$LOG_FILE" ]; then return; fi
# ... rest of rotation logic
}Should-fix issue (MEDIUM: 85% confidence) — flagged by consistency and architecture reviewers.
Code Review Summary: #161 - Learning System Validation & Debug LogsInline Comments (High Confidence: ≥80%)4 inline comments created above for distinct blocking and should-fix issues:
Lower-Confidence Findings (60-79%)The following issues were found during review but fall below the 80% inline-comment threshold: Code Quality
Type Safety
Documentation & Testing
Skipped Findings (Pre-existing / Tracked)
4 inline comments created. Generated by Claude Code review system. |
…ation, docs - Extract shared log-paths helper (eliminates 5x slug duplication, adds chmod 700) - Strengthen EXISTING_OBS jq filter (type enum + obs_ prefix checks) - Use parseLearningLog for auto-purge in post-install (validation consistency) - Inject devflowDir parameter for test filesystem isolation - Extract loadAndCountObservations helper (DRY --status/--list/--purge) - Align rotate_log guard clause in background-memory-update - Add --purge to README, update --configure description, CHANGELOG entries
Summary
Fixes the learning system's background-learning script where Sonnet returns observations with empty fields, causing a self-matching feedback loop. Adds field validation, transcript quality gate, EXISTING_OBS filtering, debug mode, and relocates debug logs to user scope (~/.devflow/logs/).
Changes
Related Issues
Relates to learning system validation and debug infrastructure
Testing