Skip to content

fix(brain-cache): normalize valid-but-partial _meta.json so consumers don't crash#1880

Open
jbetala7 wants to merge 1 commit into
garrytan:mainfrom
jbetala7:oss/fix-1879-brain-cache-meta-normalize
Open

fix(brain-cache): normalize valid-but-partial _meta.json so consumers don't crash#1880
jbetala7 wants to merge 1 commit into
garrytan:mainfrom
jbetala7:oss/fix-1879-brain-cache-meta-normalize

Conversation

@jbetala7

@jbetala7 jbetala7 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Problem

bin/gstack-brain-cache crashes with a TypeError when its _meta.json is valid JSON but lacks the last_refresh map. loadMeta already starts fresh for a missing file and for corrupt (unparseable) JSON, but a file that parses successfully is returned verbatim with no normalization. Three consumers then dereference meta.last_refresh unguarded:

  • isStale (reached via cmdGet's warm-path check) — meta.last_refresh[entityName]
  • cmdInvalidatedelete meta.last_refresh[entityName]
  • refreshEntitymeta.last_refresh[entityName] = Date.now()

refreshEntity already guards the sibling map (meta.last_attempt = meta.last_attempt || {}), and the CacheMeta type marks last_attempt? optional while last_refresh is required — so the guard was applied to only one of the two maps.

Repro (on main)

A _meta.json whose schema_version and endpoint_hash match the current pack (so the schema/endpoint rebuild path is not taken) but with no last_refresh key:

  • cmdGet('product', 'helsinki')TypeError: undefined is not an object (evaluating 'meta.last_refresh[entityName]')
  • cmdInvalidate('product', 'helsinki')TypeError: undefined is not an object (evaluating 'delete meta.last_refresh[entityName]')

Such a meta can arise from external tooling, a hand-edit, or any valid-but-partial persisted state — the same class of input the corrupt-JSON branch already guards against.

Root cause

loadMeta normalizes the missing-file and corrupt-JSON cases but returns a valid-but-partial meta as-is, leaving last_refresh/last_attempt possibly undefined for downstream consumers.

Fix

Normalize both maps once in loadMeta so a partial file degrades like the corrupt-file case instead of crashing. A meta without last_refresh is now treated as never-refreshed: cmdGet falls through to a normal cold-refresh/stale path and cmdInvalidate is a safe no-op.

Testing

  • bun test test/brain-cache-roundtrip.test.ts test/brain-cache-spec.test.ts → 32 pass / 0 fail
  • Added two regression tests (cmdGet/cmdInvalidate against a _meta.json lacking last_refresh); both fail on main with the TypeError above and pass with this change.

Fixes #1879

🤖 Generated with Claude Code

… don't crash

loadMeta starts fresh when _meta.json is missing or corrupt (unparseable),
but returned a valid-but-partial meta verbatim. A meta that parses yet lacks
the last_refresh map (external tooling, hand-edit, partial-but-valid state)
then crashed every consumer that dereferences it:

- isStale (via cmdGet warm-path): meta.last_refresh[entityName]
- cmdInvalidate: delete meta.last_refresh[entityName]
- refreshEntity: meta.last_refresh[entityName] = Date.now()

refreshEntity already guarded its sibling map (last_attempt = last_attempt || {}),
and the type marks last_attempt optional but last_refresh required, so the
guard was applied to only one of the two maps. Normalize both once in loadMeta
so a partial file degrades like the corrupt-file case instead of throwing.

Adds regression tests covering cmdGet and cmdInvalidate against a _meta.json
that lacks last_refresh.

Fixes garrytan#1879

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jun 5, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gstack-brain-cache: cmdGet/cmdInvalidate crash on a valid _meta.json that lacks last_refresh

1 participant