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_degrees → degree_id → analyze_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
get_degree / search_degrees resolve the row → DB connection, auth, and the programs record are fine.
analyze_degree with a cache: handle succeeds → the analyzer and the degree_id dispatch (cache branch) are fine.
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)
- 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.
- 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.
- 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."
- 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.
- 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.
- 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.
- Verify the dispatch split. Confirm
cache: routes to the in-process cache (works) and non-cache: routes to the DB resolver (fails).
- Test key encoding. If querying by
program_key, verify : and | are encoded correctly in the request and round-trip.
- 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)
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.
- Wrong relation — queries a different table/view/RPC (e.g., an analysis-runs/cache relation) than the
programs table get_degree reads.
- 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.
- 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."
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) |
nuanalytics —
analyze_degreecan't resolve a storeddegree_idThis is a debugging brief for the
analyze_degreeDB-resolution failure: a stored degree is found byget_degree/search_degrees, butanalyze_degreerejects the samedegree_id, forcing acache_yamlworkaround. The orientation paragraph above is for you — everything below the marker is self-contained and written to be pasted straight into Claude Code in/planmode (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 (
/planmode)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_degreesandget_degreeresolve a stored program correctly, butanalyze_degreereturns{"error":"degree_id not found"}when passed the same identifiers — both thedegree_idslug and theprogram_key. The analyzer is healthy: the identical degree cached viacache_yamland passed back as acache:handle analyzes fine. So the fault is isolated toanalyze_degree's DB-degree_idresolution branch — not the analyzer, not the DB connection, not the data. This blocks the intended DB-first workflow (search_degrees→degree_id→analyze_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, andget_institution_completionsall 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
get_degree/search_degreesresolve the row → DB connection, auth, and theprogramsrecord are fine.analyze_degreewith acache:handle succeeds → the analyzer and thedegree_iddispatch (cache branch) are fine.analyze_degreewith the DBdegree_id(both forms) fails withdegree_id not found→ the bug is inanalyze_degree's DB-degree_idresolution 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)
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 thecache:result (population_size6,912,000; complexity median ≈ 148; non-emptyselected_plans).analyze_degree(degree_id="prog:167358|11.0701|2025-2026|BS")(program_key form) also succeeds.degree_idworks forrender_plan_graph,audit_degree, andgenerate_degree_report.degree_idreturns a fast, descriptive error (no multi-minute hang) naming the key forms attempted and the relation queried.cache:handle calls still succeed.Investigate (roughly this order)
degree_id not found(and variants likenot found,DegreeNotFound). Identify the emitting function and the lookup immediately preceding it — that lookup is the bug site.get_degree/search_degreesagainstanalyze_degree'sdegree_idbranch. Same shared resolver? same table/view/RPC? same client/credentials/headers? same filter columns and precedence? Record every divergence.get_degreereadsprograms. Verify whatanalyze_degreereads — if it's a different table/view or an analysis-runs/cache relation, that likely explains "found the degree but not the analysis runs."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.degree_idbranch: key forms attempted, relation queried, auth role/JWT claims presented, rows returned. Re-run steps 3–4.analyze_degreequeries, under the role/JWT it presents; compare to the policy that letsget_degreesucceed. A different client/role inanalyze_degreeis the prime RLS suspect givenget_degreenow works.cache:routes to the in-process cache (works) and non-cache:routes to the DB resolver (fails).program_key, verify:and|are encoded correctly in the request and round-trip.degree_idtools 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)
analyze_degreedoesn't shareget_degree's resolver — runs its own single-column exact match and lacks theprogram_key → slug → natural keyprecedence/normalization, so neither form matches.programstableget_degreereads.degree_iderrors on miss instead of falling back to loading theprogramsdocument and generating.program_keyencoding —:/|mis-encoded in the PostgREST filter, yielding zero rows.Candidate fix direction (validate against findings)
degree_idtool delegate to the same resolution functionget_degreeuses (precedence + normalization + correct encoding), return the unifieddocument, and feed it into the analyzer via the exact path thecache:handle already exercises.cache_yamlfallback); never hang on a resolver miss.Your plan should contain
degree_idtools this fix covers vs. which need separate work.Reference — identifiers to test against
prog:167358|11.0701|2025-2026|BSnortheastern-university-bachelor-of-science-in-computer-science-concentrationcache:69dfe60ac6ecb89a(in-process, 24h TTL)