/insights crashes with TypeError: Object.keys(j) when facet file contains API error response

Resolved 💬 3 comments Opened Feb 6, 2026 by reggiechan74 Closed Feb 10, 2026

Environment

  • Claude Code version: Latest (as of 2026-02-06)
  • OS: Linux (Codespace), also reproducible on other platforms
  • Command: /insights

Bug Description

Running /insights crashes with:

TypeError: undefined is not an object (evaluating 'Object.keys(j)')

The minified variable j corresponds to one of three aggregation objects that /insights iterates over when building the report:

  • goal_categories
  • friction_counts
  • user_satisfaction_counts

Root Cause

A facet JSON file in ~/.claude/usage-data/facets/ contained an API error response instead of valid facet data. Specifically, a session that encountered an invalid_request_error (malformed JSON with a bad Unicode surrogate) had its error response written to disk as the facet file:

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "The request body is not valid JSON: no low surrogate in string: line 1 column 4023 (char 4022)"
  },
  "request_id": "req_011CXrvp8Uz8VBDkL1mNLnJ5",
  "session_id": "5915c52a-e588-4112-a815-423546cc215f"
}

This file is missing all three expected object fields (goal_categories, friction_counts, user_satisfaction_counts). When /insights runs Object.keys() on these undefined properties during cross-session aggregation, it crashes.

Additional complication

The corrupted facet file was continuously regenerated by the active session. Because the session's conversation context contained a bad Unicode surrogate, every subsequent facet generation API call failed with the same error, and the error response was re-saved to disk. This means:

  1. Deleting the file doesn't fix it — it gets recreated immediately
  2. Replacing it with valid facet data only works temporarily until the next facet generation attempt overwrites it

Steps to Reproduce

  1. Have a session that triggers an API error during facet generation (e.g., conversation context with invalid Unicode surrogates, or any invalid_request_error)
  2. The error response gets saved as ~/.claude/usage-data/facets/<session-id>.json
  3. Run /insights
  4. Crash: TypeError: undefined is not an object (evaluating 'Object.keys(j)')

Diagnostic Script

Run this to identify corrupted facet files:

import json, glob, os

for f in sorted(glob.glob(os.path.expanduser('~/.claude/usage-data/facets/*.json'))):
    with open(f) as fh:
        data = json.load(fh)
    fname = os.path.basename(f)

    # Check for API error responses saved as facet files
    if data.get('type') == 'error':
        print(f'{fname}: API error response (not facet data)')

    # Check for missing expected keys
    expected = ['goal_categories', 'friction_counts', 'user_satisfaction_counts']
    missing = [k for k in expected if k not in data]
    if missing:
        print(f'{fname}: missing keys {missing}')

    # Check for null values on expected keys
    for k in expected:
        if k in data and data[k] is None:
            print(f'{fname}: {k} is null')

User Workaround

Option 1: Delete corrupted facet files (only works from a different session)

python3 -c "
import json, glob, os
for f in sorted(glob.glob(os.path.expanduser('~/.claude/usage-data/facets/*.json'))):
    with open(f) as fh:
        data = json.load(fh)
    if data.get('type') == 'error' or 'goal_categories' not in data:
        print(f'Removing corrupted facet: {os.path.basename(f)}')
        os.remove(f)
"

Important: If the corrupted file belongs to the current active session, deletion alone won't work — the file gets immediately recreated with the same error response on the next facet generation attempt. You must start a new session before deleting.

Option 2: Replace with valid minimal facet structure (works from any session)

This is the fix that actually resolved the issue. Replacing the error response with a valid empty facet structure satisfies /insights and survives being in the active session (the next successful facet generation will overwrite it with real data):

python3 -c "
import json, glob, os
for f in sorted(glob.glob(os.path.expanduser('~/.claude/usage-data/facets/*.json'))):
    with open(f) as fh:
        data = json.load(fh)
    if data.get('type') == 'error' or 'goal_categories' not in data:
        session_id = data.get('session_id', os.path.splitext(os.path.basename(f))[0])
        fix = {
            'goal_categories': {},
            'friction_counts': {},
            'user_satisfaction_counts': {},
            'session_id': session_id
        }
        with open(f, 'w') as fh:
            json.dump(fix, fh, indent=2)
        print(f'Fixed corrupted facet: {os.path.basename(f)}')
"

This was the approach that resolved the crash in practice.

Suggested Fix

Two issues should be addressed:

1. /insights should handle missing/malformed facet data gracefully

The aggregation code that calls Object.keys() on goal_categories, friction_counts, and user_satisfaction_counts should guard against undefined, null, or non-object values:

// Instead of:
Object.keys(facet.goal_categories).forEach(...)

// Use:
Object.keys(facet.goal_categories || {}).forEach(...)

// Or more defensively:
if (facet.goal_categories && typeof facet.goal_categories === 'object') {
  Object.keys(facet.goal_categories).forEach(...)
}

This should be applied to all three fields (goal_categories, friction_counts, user_satisfaction_counts).

2. Facet generation should not write API error responses to disk as facet files

When the facet generation API call returns an error, the error response should be discarded (or logged separately) rather than saved to ~/.claude/usage-data/facets/<session-id>.json. The write path should validate that the response contains expected facet structure before persisting.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗