Skip to content

analyze_degree can't resolve a stored degree_id #16

Description

@lionelle

nuanalytics — analyze_degree can't resolve a stored degree_id

This is a debugging brief for the analyze_degree DB-resolution failure: a stored degree is found by get_degree/search_degrees, but analyze_degree rejects the same degree_id, forcing a cache_yaml workaround. The orientation paragraph above is for you — everything below the marker is self-contained and written to be pasted straight into Claude Code in /plan mode (it's a little long by design; the evidence is what makes the plan good).

Note, I generated this by attempting to run the MCP in claude code desktop, and via command line within WebScrapperCombined. Both produced similar issues, so had it auto generate this report. You may want to modify, work with a solution differently.


PASTE FROM HERE INTO CLAUDE CODE (/plan mode)

You are working in the nuanalytics MCP server repository. We are in plan mode: investigate read-only and return a remediation plan. Do not modify code yet. Begin by orienting yourself in the repo (language, DB layer, where MCP tools are defined) before reasoning about the fix.

The bug

After the database connection was restored, search_degrees and get_degree resolve a stored program correctly, but analyze_degree returns {"error":"degree_id not found"} when passed the same identifiers — both the degree_id slug and the program_key. The analyzer is healthy: the identical degree cached via cache_yaml and passed back as a cache: handle analyzes fine. So the fault is isolated to analyze_degree's DB-degree_id resolution branch — not the analyzer, not the DB connection, not the data. This blocks the intended DB-first workflow (search_degreesdegree_idanalyze_degree) for every stored degree.

Backend is Supabase/PostgREST (prior failures surfaced PGRST303; RLS is in play). The DB is currently healthy — search_degrees, get_degree, and get_institution_completions all succeed in the same session, so this is not an outage or expired-JWT problem.

Why it's well-isolated — anchor your investigation on these three facts

  1. get_degree / search_degrees resolve the row → DB connection, auth, and the programs record are fine.
  2. analyze_degree with a cache: handle succeeds → the analyzer and the degree_id dispatch (cache branch) are fine.
  3. analyze_degree with the DB degree_id (both forms) fails with degree_id not found → the bug is in analyze_degree's DB-degree_id resolution branch specifically.
    This is an asymmetry between two DB code paths (get_degree's resolver vs. analyze_degree's resolver), not a data problem.

Reproduction (observed this session)

# WORKS — returns the stored program
get_degree(unitid=167358, cip_code="11.0701", catalog_year="2025-2026")
# -> program_key: "prog:167358|11.0701|2025-2026|BS"
#    degree_id:   "northeastern-university-bachelor-of-science-in-computer-science-concentration"
#    (get_degree precedence: program_key -> degree_id slug -> natural key (unitid,cip_code,catalog_year))
 
# FAILS — slug form
analyze_degree(degree_id="northeastern-university-bachelor-of-science-in-computer-science-concentration")
# -> {"error":"degree_id not found"}
 
# FAILS — program_key form
analyze_degree(degree_id="prog:167358|11.0701|2025-2026|BS")
# -> {"error":"degree_id not found"}
 
# WORKS — same degree, cache handle
cache_yaml(yaml_content=<unified YAML>) -> {"handle":"cache:69dfe60ac6ecb89a"}
analyze_degree(degree_id="cache:69dfe60ac6ecb89a")
# -> {"success":true,"population_size":6912000,"complexity":{"median":148,...},"selected_plans":[...]}

Definition of done (the plan's fix must satisfy all)

  • analyze_degree(degree_id="northeastern-university-bachelor-of-science-in-computer-science-concentration") succeeds with output equivalent to the cache: result (population_size 6,912,000; complexity median ≈ 148; non-empty selected_plans).
  • analyze_degree(degree_id="prog:167358|11.0701|2025-2026|BS") (program_key form) also succeeds.
  • The same DB degree_id works for render_plan_graph, audit_degree, and generate_degree_report.
  • An unknown degree_id returns a fast, descriptive error (no multi-minute hang) naming the key forms attempted and the relation queried.
  • Regression: existing cache: handle calls still succeed.

Investigate (roughly this order)

  1. Find the error origin. Grep for the literal degree_id not found (and variants like not found, DegreeNotFound). Identify the emitting function and the lookup immediately preceding it — that lookup is the bug site.
  2. Diff the two resolvers. Compare the DB access in get_degree/search_degrees against analyze_degree's degree_id branch. Same shared resolver? same table/view/RPC? same client/credentials/headers? same filter columns and precedence? Record every divergence.
  3. Confirm the target relation. Determine exactly which relation each path reads. get_degree reads programs. Verify what analyze_degree reads — if it's a different table/view or an analysis-runs/cache relation, that likely explains "found the degree but not the analysis runs."
  4. Reproduce the raw query. Run analyze_degree's exact query directly against the DB for both keys (program_key = 'prog:167358|11.0701|2025-2026|BS' and the slug). Log the final PostgREST URL/SQL, bound params, and row count.
  5. Instrument the resolver. Temporarily log, in the degree_id branch: key forms attempted, relation queried, auth role/JWT claims presented, rows returned. Re-run steps 3–4.
  6. Check RLS. Inspect SELECT policies on whatever relation analyze_degree queries, under the role/JWT it presents; compare to the policy that lets get_degree succeed. A different client/role in analyze_degree is the prime RLS suspect given get_degree now works.
  7. Verify the dispatch split. Confirm cache: routes to the in-process cache (works) and non-cache: routes to the DB resolver (fails).
  8. Test key encoding. If querying by program_key, verify : and | are encoded correctly in the request and round-trip.
  9. Fan-out. Check whether the other DB-degree_id tools share the broken resolver: render_plan_graph, audit_degree, generate_degree_report, get_course_detail, find_courses_matching, degree_pipeline, compare_degrees.

Leading hypotheses (confirm or rule out — don't assume)

  1. analyze_degree doesn't share get_degree's resolver — runs its own single-column exact match and lacks the program_key → slug → natural key precedence/normalization, so neither form matches.
  2. Wrong relation — queries a different table/view/RPC (e.g., an analysis-runs/cache relation) than the programs table get_degree reads.
  3. No fallback — a pre-flight stored-run/cache lookup keyed by degree_id errors on miss instead of falling back to loading the programs document and generating.
  4. Different DB client / RLS context — different Supabase client, key (anon vs. service role), or headers/JWT, so RLS returns zero rows → surfaced as "not found."
  5. program_key encoding:/| mis-encoded in the PostgREST filter, yielding zero rows.

Candidate fix direction (validate against findings)

  • One shared resolver: make every DB-degree_id tool delegate to the same resolution function get_degree uses (precedence + normalization + correct encoding), return the unified document, and feed it into the analyzer via the exact path the cache: handle already exercises.
  • One DB client/context everywhere so RLS behaves identically across tools.
  • Fail loud, fail fast: on a genuine miss, return a diagnostic listing attempted keys + relation queried (and suggest the cache_yaml fallback); never hang on a resolver miss.

Your plan should contain

  • Confirmed root cause with file/function/line references.
  • Exact change set (files + what changes in each).
  • Any DB/RLS/migration implications.
  • Tests to add, mapped to each Definition-of-done item.
  • Fan-out: which other degree_id tools this fix covers vs. which need separate work.
  • Risks and rollback.

Reference — identifiers to test against

Field Value
program_key prog:167358|11.0701|2025-2026|BS
degree_id (slug) northeastern-university-bachelor-of-science-in-computer-science-concentration
UNITID 167358 (Northeastern University)
CIP (curriculum) 11.0701
catalog_year 2025-2026
degree_type BS
total_credits 134
working workaround handle cache:69dfe60ac6ecb89a (in-process, 24h TTL)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions