fix(analysis): negative-cache failed generations so they stop minting sessions - #14
Merged
Merged
Conversation
… sessions
The daily/weekly analysis generator already gated on success
(analyses/{key}.json exists -> skip). But _run_analysis only wrote the
cache on a clean returncode + parseable JSON. A day whose generation
failed (non-zero exit, unparseable output, timeout) left no trace, so
trigger_analysis_generation re-spawned a fresh headless 'claude --print'
session for it on EVERY dashboard load -- unbounded session minting.
Add a {key}.failed negative-cache marker:
- _analysis_blocked(key): skip if a cached analysis exists OR a failure
marker is still within ANALYSIS_FAIL_COOLDOWN (24h)
- _run_analysis records a failure on every error path, and clears the
marker on success (self-healing: one retry allowed per cooldown)
- gate points in trigger_analysis_generation now use _analysis_blocked
Markers use a .failed suffix, so the /api/analyses *.json glob never
serves them. Adds tests/test_analysis_cache.py (6 cases, stdlib unittest,
no subprocess/server boot).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a negative-cache for failed analysis generations so the dashboard doesn’t continuously spawn new headless Claude sessions for periods that repeatedly fail to generate.
Changes:
- Introduces
{key}.failedmarker files with a 24h cooldown gate to skip re-generation after failures. - Records failure markers on all
_run_analysiserror paths and clears them on success. - Adds focused
unittestcoverage for the gating/marker behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| serve.py | Adds failure-marker helpers and uses _analysis_blocked to gate generation; records/clears markers in _run_analysis. |
| tests/test_analysis_cache.py | Adds unit tests covering success caching, failure cooldown behavior, marker expiry, and API glob safety. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+608
to
+611
| try: | ||
| last = float(marker.read_text().strip() or 0) | ||
| except (ValueError, OSError): | ||
| last = 0.0 |
Comment on lines
686
to
+690
| if not _analysis_semaphore.acquire(timeout=60): | ||
| return | ||
| try: | ||
| out_path = ANALYSES_DIR / f"{key}.json" | ||
| if out_path.exists(): | ||
| if _analysis_blocked(key): |
Comment on lines
+24
to
+31
| def setUp(self): | ||
| self._tmp = tempfile.TemporaryDirectory() | ||
| self.dir = Path(self._tmp.name) | ||
| # Redirect all analysis I/O at the temp dir for the duration of the test. | ||
| serve.ANALYSES_DIR = self.dir | ||
|
|
||
| def tearDown(self): | ||
| self._tmp.cleanup() |
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.
Problem
Tribbles' daily/weekly analysis generator spawns a headless
claude --printsession per period to writeanalyses/{key}.json(rendered by the visualizer). Each such run mints a permanent Claude Code session transcript under~/.claude/projects/.The success path was already gated:
trigger_analysis_generation()skips any key whoseanalyses/{key}.jsonexists. But_run_analysisonly wrote that cache file on a cleanreturncode==0+ parseable JSON. A day whose generation failed (non-zero exit, unparseable output, timeout) left no trace, so it re-spawned a fresh session on every dashboard load — unbounded session minting.Fix
Negative-cache failures with a
{key}.failedmarker:_analysis_blocked(key)— skip generation if a cached analysis exists or a failure marker is still withinANALYSIS_FAIL_COOLDOWN(24h)._run_analysisrecords a failure on every error path and clears the marker on success → self-healing: at most one retry per cooldown instead of one per page load.trigger_analysis_generationnow use_analysis_blocked..failedsuffix, so the/api/analyses*.jsonglob never serves them as analyses.Behavior change
Tests
tests/test_analysis_cache.py— 6 stdlibunittestcases (no subprocess, no server boot): cold cache allows, success blocks, failure negative-cached within cooldown, marker expires after cooldown (one retry), success clears marker, markers not served as*.json.🤖 Generated with Claude Code