fix(store): guard store_findings against None engagement_score on update path#796
Open
nicolefinateri wants to merge 1 commit into
Open
Conversation
…e path
`.get("engagement_score", 0)` only substitutes 0 for an *absent* key, so a
present-but-None value reached `max(None, existing)` on the update branch and
raised TypeError ("'>' not supported between instances of 'float' and
'NoneType'"). Guard `new_engagement` with `or 0`, symmetric with the
existing-row side already guarded on the same line.
The standard `findings_from_report` pipeline coerces None->0 at the producer,
so this hardens the public `store_findings(List[Dict[str, Any]])` boundary for
arbitrary callers rather than fixing a reachable pipeline crash. Adds a
regression test in tests/test_store.py.
Contributor
Greptile SummaryThis PR makes
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (1): Last reviewed commit: "fix(store): guard new_engagement against..." | Re-trigger Greptile |
23241a6749
added a commit
to 23241a6749/last30days-skill
that referenced
this pull request
Jul 14, 2026
… TypeError on arithmetic/sort/sum/max
dict.get(key, default) only substitutes the default when the key is
absent. A key that exists with value None passes through, producing
None instead of the intended default. This causes TypeError in:
- arithmetic (reactions + comments)
- list.sort() with key function
- max() comparisons
- sum() over generator expressions
Fixes 17 occurrences across 8 files:
- github.py: item.get("comments", 0) used in reactions + comments
- briefing.py: f.get("engagement_score", 0) used in max()/sum()
- youtube_yt.py: .get("views", 0) inside sort key
- instagram.py: same sort-key pattern
- tiktok.py: same sort-key pattern
- hackernews.py: .get("points", 0) in sort key
- signals.py: .get("views", 0) in engagement floor check
- store.py: .get("engagement_score", 0) / .get("relevance_score", 0)
in store_sightings (same root cause as PR mvanhorn#796)
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.
What
store_findingsraisesTypeErrorwhen a re-sighted finding carriesengagement_score: None.Why
On the update branch the incoming score is read with a defaulted
.get, then compared:dict.get(key, 0)only substitutes0when the key is absent. A key that is present with valueNonepasses straight through, sonew_engagementbecomesNoneandmax(None, <float>)raises:Note the asymmetry: the
existingside on line 430 is already guarded withor 0, but thenew_engagementside is not.engagement_scoreis declaredfloat | Noneinlib/schema.py, andlib/fusion.pycan re-introduceNoneviametadata.get("engagement_score"), soNoneis a first-class runtime value for this field.Scope (honest framing)
This is boundary hardening, not a fix for a crash reachable through the normal pipeline. The two producers that feed
store_findingsviafindings_from_report—finding_from_candidate(candidate.engagement or 0) and the supplement dict (item.engagement_score or 0.0) — already coerceNone → 0before the dict is built. Butstore_findings(List[Dict[str, Any]])is a public function that accepts arbitrary dicts, so any other or future caller passing a raw finding withengagement_score: Nonehits the crash. This makes the boundary null-safe and symmetric with line 430.Fix
Test
Adds
test_store_findings_none_engagement_on_update_does_not_crashtotests/test_store.py: it stores a finding with a real score, re-sights the same URL withengagement_score: None, and asserts no crash plus thatmax(None→0, existing)preserves the higher real score. The test reproduces theTypeErroronmainand passes with this change.Verified locally (Python 3.12.13, pytest 9.1.0, deps from
uv.lock):tests/test_store.py— 28 passed (the 27 existing tests + this one)test_store,test_internals_v3,test_watchlist_commands,test_fusion_v3,test_signals_v3,test_briefing_v3,test_schema_v3) — 179 passed, 0 failed