fix(learn): refresh context-file timestamp when LLM returns no new recommendations#259
Open
gglucass wants to merge 2 commits intochopratejas:mainfrom
Open
Conversation
…commendations When `headroom learn` produced zero recommendations, the writers short- circuited via `if context_recs:` / `if memory_recs:` / `if not recommendations:`, so a successful run that had nothing new to report left `CLAUDE.md` / `MEMORY.md` / `AGENTS.md` / `GEMINI.md` completely untouched — including the `*Auto-generated by headroom learn on YYYY-MM-DD*` timestamp. Users had no visible signal that the run actually happened. `_merge_into_file` now returns `str | None` — `None` only when there are no new recommendations AND no existing marker block to refresh. The three writers drop their leading `if ...recs:` guards and skip based on the `None` return instead. When a marker block already exists, the block's timestamp and any carried-forward prior sections are always re-emitted with today's date. When no block exists and no recs are produced, nothing is written (no empty-block creation). Adds 11 unit tests covering the four writer paths and the direct `_merge_into_file` contract.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When
headroom learnproduces zero recommendations (LLM returns emptycontext_file_rules/memory_file_rulesarrays), the writersshort-circuit before touching the target files — so a successful run
that had nothing new to report leaves
CLAUDE.md/MEMORY.md/AGENTS.md/GEMINI.mdcompletely untouched, including the*Auto-generated by headroom learn on YYYY-MM-DD — do not edit manually*timestamp. Users have no visible signal that the run actually happened
— looking at the file, it's indistinguishable from "learn never ran."
I hit this today:
headroom learnon a project with 155 sessions /10,732 tool calls completed successfully with
"No actionable patterns found."and exit 0, but the timestamp in myCLAUDE.mdstayed frozenat the previous run's date. It looked broken even though it wasn't.
This PR drops the leading
if context_recs:/if memory_recs:/if not recommendations:guards inClaudeCodeWriter,CodexWriter,and
GeminiWriter, and moves the skip decision into_merge_into_fileso the "there's a marker block to refresh" case is handled uniformly
across all four output files.
Fixes #(no linked issue — surfaced organically while iterating on
headroom learnin headroom-desktop)Type of Change
Changes Made
_merge_into_filesignature:str→str | None. ReturnsNoneonly when there are no new recommendations AND no existing marker
block to refresh. Docstring updated to spell out the contract.
ClaudeCodeWriter,CodexWriter,GeminiWriter: drop the leadingif …recs:/if not recommendations:guards. Call_merge_into_fileunconditionally; skip the write when it returnsNone. Net result: when a marker block exists, today's date and thecarried-forward sections are always re-emitted; when no block exists
and no recs are produced, nothing is written (no empty-block
creation).
ClaudeCodeWriterCLAUDE.md + MEMORY.md,
CodexWriterAGENTS.md,GeminiWriterGEMINI.md) plus direct
_merge_into_filecontract tests.[Unreleased] → Fixed.Testing
pytest)ruff check .)mypy headroom)Manual end-to-end was not run against the patched code in this PR —
the fix is test-covered and the behavior is small enough to validate
from the unit-test surface. Happy to run an end-to-end pass if a
reviewer wants confirmation.
Test Output
Representative new tests from
tests/test_learn/test_writer.py:Checklist
Documentation checkbox left unchecked because this is an internal
behavior fix with no user-facing docs section to update (behavior is
not spec'd in the README). The
_merge_into_filedocstring wasupdated inline and the CHANGELOG covers the user-visible change.
Screenshots (if applicable)
N/A — behavior change is a timestamp string in a markdown file.
Additional Notes
Signature change on
_merge_into_file(private, leading underscore) isnot a public-API break. The three writers are the only callers.
The section-level carry-forward behavior from #231 is preserved:
prior-section carry-forward continues to work, and a re-run with new
recs still wins over same-named prior sections.