Skip to content

Bug: NULL bubble value crashes GET /tabs with TypeError #50

@bradjin8

Description

@bradjin8

Repository: cppa-cursor-browser
Reported: 2026-05-18
Severity: High


Summary

Navigating into a project by clicking an item in the "Projects with Conversations" table triggers a GET /api/workspaces/<id>/tabs request that crashes with a 500 error, making the project detail view completely inaccessible.


Reproduction Steps

  1. Run the app locally.
  2. Open the main dashboard.
  3. Click any project in the "Projects with Conversations" table.
  4. Observe a 500 response — the project detail view does not load.

Error Traceback

GET /api/workspaces/70dab26a3ac44c481f00d49e068473e7 HTTP/1.1" 200 -
Failed to get workspace tabs
Traceback (most recent call last):
  File "api/workspaces.py", line 149, in get_workspace_tabs
    payload, status = assemble_workspace_tabs(workspace_id, workspace_path, rules)
  File "services/workspace_tabs.py", line 101, in assemble_workspace_tabs
    bubble_obj = Bubble.from_dict(json.loads(row["value"]), bubble_id=bid)
TypeError: the JSON object must be str, bytes or bytearray, not NoneType

Root Cause

In services/workspace_tabs.py, the bubble-loading loop (lines 96–109) queries cursorDiskKV for all bubbleId: keys and immediately calls json.loads(row["value"]) without a None guard:

# services/workspace_tabs.py  lines 96–109
for row in _safe_fetchall("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'"):
    parts = row["key"].split(":")
    if len(parts) >= 3:
        bid = parts[2]
        try:
            bubble_obj = Bubble.from_dict(json.loads(row["value"]), bubble_id=bid)  # ← row["value"] can be None
            bubble_map[bid] = bubble_obj.raw
        except SchemaError as e:
            print(f"Schema drift in bubble {bid}: {e}")
        except (json.JSONDecodeError, ValueError):   # ← TypeError is NOT caught
            pass

When a cursorDiskKV row has a NULL value column, row["value"] resolves to None. json.loads(None) raises TypeError, which the current except clause does not handle. The exception propagates up through assemble_workspace_tabs and the API handler returns a 500.


Image

Acceptance Criteria

  • row["value"] is None rows are skipped so a single NULL bubble row cannot crash the tabs endpoint
  • GET /api/workspaces/<id>/tabs returns 200 (with the bad row silently skipped) for workspaces containing NULL bubble values
  • Fix is covered by a unit test with a mock cursorDiskKV row whose value is NULL / None
  • No regression on workspaces without NULL values

Affected Files

  • services/workspace_tabs.py — lines 96–109 (bubble-loading loop)
  • api/workspaces.py — line 149 (call site)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions