From c024a5b347be8251c251fafad6c98d8d2c89d088 Mon Sep 17 00:00:00 2001 From: Stefan Andrei <89592870+sneakygriff@users.noreply.github.com> Date: Sat, 15 Aug 2026 23:45:25 +0300 Subject: [PATCH] fix(brain-cache): honest 'missing' instead of fabricated-empty digests on gbrain failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A gbrain-unreachable failure in fetchRecentDecisions and fetchSalience used to be converted into a cached 'successful' empty digest ("_No prior skill runs recorded._" / "_No salient pages in last 14d._") that refreshEntity stamped with last_refresh. The false negative then survived every subsequent TTL cycle, indistinguishable from a genuine zero-rows result. Now failure returns null, so cmdGet's existing missing/stale-fallback machinery reports the true state — matching what fetchGoals and fetchSimplePage already do on failure. Also adds an Array.isArray guard in fetchRecentDecisions so a malformed payload ({pages: {}} etc.) classifies as failure instead of crashing refreshEntity mid-refresh; a genuinely empty pages array still renders the honest empty digest. Co-Authored-By: Claude Fable 5 --- bin/gstack-brain-cache | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/bin/gstack-brain-cache b/bin/gstack-brain-cache index f7694f33f..301483632 100755 --- a/bin/gstack-brain-cache +++ b/bin/gstack-brain-cache @@ -521,6 +521,21 @@ function fetchRecentDecisions(projectSlug: string | null): string | null { '--json', ]); if (!result?.pages) { + // F10 bug fix: this branch used to return the hardcoded + // "_No prior skill runs recorded._" string here, which is indistinguishable + // from a genuine zero-rows result. That silently converted a gbrain- + // unreachable FAILURE into a "successful" cached digest — refreshEntity() + // would write it and stamp last_refresh, so the false negative survived + // every subsequent TTL cycle forever. Returning null instead lets cmdGet's + // existing missing/stale-fallback machinery report the true state, exactly + // like every sibling fetcher (fetchGoals, fetchSimplePage) already does on + // failure. + return null; + } + // A malformed payload ({pages: {}} etc.) must classify as failure, not crash + // refreshEntity mid-refresh — same honest-missing polarity as the F10 fix. + if (!Array.isArray(result.pages)) return null; + if (result.pages.length === 0) { return `# Recent decisions (project: ${projectSlug})\n\n_No prior skill runs recorded._\n`; } const lines = result.pages.map((p) => `- ${p.title || p.slug}`); @@ -576,7 +591,17 @@ function fetchSalience(projectSlug: string | null): string | null { '--limit', '10', '--json', ]); - if (!result?.pages) return `# Recent salience\n\n_No salient pages in last 14d._\n`; + // F10 bug fix (sibling of fetchRecentDecisions above): a gbrain-unreachable + // failure used to render the identical hardcoded "no salient pages" string + // as a genuine empty result, which refreshEntity() then cached as if it + // were verified truth. Unlike recent-decisions there is no project-local + // fallback for salience — it is specifically gbrain's emotional-weight- + // ranked *brain* pages, not project decision/work data, and conflating the + // two would defeat the D9 privacy allowlist's purpose. So on failure we + // return null and let the cache report 'missing' (same as product.md, + // goals.md, etc. already do on this machine) instead of asserting a claim + // we have no way to verify. + if (!result?.pages) return null; // D9 privacy gate: strip entries outside the allowlist BEFORE rendering. // Sensitive personal content (family, therapy, reflection) is never written