Skip to content

feat(studio): grounded + AI Issue explainer - #215

Merged
prisis merged 7 commits into
alphafrom
feat/studio-issue-explainer
Jul 28, 2026
Merged

feat(studio): grounded + AI Issue explainer#215
prisis merged 7 commits into
alphafrom
feat/studio-issue-explainer

Conversation

@prisis

@prisis prisis commented Jul 27, 2026

Copy link
Copy Markdown
Member

What

Each Studio Issue row now expands into two layers of help for the underlying error:

  1. Grounded catalog fix — derived entirely client-side from @lunora/errors (findSolutionByMessage over the Issue's sample message). Offline, instant, zero network, and always shown when the catalog recognizes the error. Now also covers Cloudflare platform error codes (520–527, 1101, 1102, …).
  2. Opt-in plain-language explanation — behind an Explain in plain language button that invokes the new explainIssue admin action. The AI is grounded strictly in the same catalog fix, and degrades gracefully to a note pointing back at the grounded fix when no AI binding is configured.

Each row owns its own expand + explanation state, so opening or explaining one Issue never touches another.

Changes by package

  • @lunora/errors — Cloudflare platform error catalog (CLOUDFLARE_PLATFORM_ERRORS, findCloudflarePlatformSolution); findSolutionByMessage falls back to it.
  • @lunora/doexplainIssue admin RPC on ShardDO, grounded in the catalog hint and calling env.AI.run directly (no @lunora/ai dependency edge); degrades to the grounded hint when no AI binding exists.
  • @lunora/studioIssueRow component merging the existing triage controls with the new expandable grounded-fix + explain detail.

Testing

  • @lunora/errors — 34 ✓ (incl. every catalog code resolves, CF-platform lookups)
  • @lunora/doshard-do.admin 107 ✓ (explainIssue grounded + degraded paths)
  • @lunora/studioissues-panel 12 ✓ (expand → grounded fix, explain → AI text, no-ai-binding degrade, no-known-fix note)
  • Full build:packages, lint:types, and lint:eslint green on all three packages.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Studio issue rows now include an expandable Suggested fix plus an opt-in Explain in plain language flow with an AI explanation section.
    • Added support for generating grounded explanations using Lunora’s catalog, with graceful degraded-state messaging when AI is unavailable or can’t explain.
    • Added curated Cloudflare platform error detection so supported Cloudflare errors surface actionable hints.
  • Bug Fixes
    • Fixed internal CDC cache key construction to avoid issues caused by embedded NUL characters.

prisis and others added 3 commits July 28, 2026 00:18
Fold Cloudflare's platform error codes (520-527, 1101, 1102, …) into the
central error catalog as curated solutions. `findCloudflarePlatformSolution`
recognizes Cloudflare's `Error <code>` phrasing (and a bare code when
`cloudflare` is also mentioned, guarded against substring false-matches), and
`findSolutionByMessage` falls back to it while Lunora message-solutions still
win. Each solution carries a likely-cause, a fix, and the family docs link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the `__lunora_admin__:explainIssue` admin op to ShardDO. It re-derives the
grounded catalog fix server-side from the sample message (never trusting a
client-supplied hint), then asks the app's `AI` binding to explain the error
strictly from those facts. With no `AI` binding wired, an empty response, or a
model error it degrades to `{ degraded: true, reason }` carrying the grounded
hint — the AI layer is additive, never the only help. Registered in the DO-side
admin registry and dispatched ahead of the Issue-triage fall-through.

Also escapes a pre-existing raw NUL separator in the aggregate-cache key as
`\u0000` (byte-identical at runtime) to satisfy the no-nul-bytes hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each Issue row now expands to two layers of help for the error. First, a
grounded catalog fix derived entirely client-side from `@lunora/errors`
(`findSolutionByMessage` over the sample message) — offline, instant, and always
shown when the catalog recognizes the error. Second, an opt-in plain-language
explanation behind a button that invokes the `explainIssue` action, grounded in
that same fix and degrading to a note pointing back at the grounded fix when no
`AI` binding is configured. Each row owns its own expand + explanation state, so
opening or explaining one Issue never touches another.

The row is extracted into an `IssueRow` component that carries both the existing
triage controls (status, severity, assignee, resolve/ignore/reopen) and the new
expandable detail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy Preview for lunorash ready!

Name Link
🔨 Latest commit bfd3eac
🔍 Latest deploy log https://app.netlify.com/projects/lunorash/deploys/6a68a5f324c54500081879ad
😎 Deploy Preview https://deploy-preview-215--lunorash.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for confirming the Contributor License Agreement! 🙏

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • api-snapshots/lunora.api.md is excluded by none and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8670dbf9-f98f-455e-a086-61e78e6c35a3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds Cloudflare platform-error hints, a reserved explainIssue admin RPC backed by optional Workers AI, and Studio issue-row controls for grounded fixes and plain-language explanations.

Changes

Issue explanation flow

Layer / File(s) Summary
Cloudflare platform error catalog
packages/errors/src/catalog.ts, packages/errors/src/index.ts
Adds structured Cloudflare error solutions, conservative message matching, public exports, and findIssueSolution fallback behavior.
Explain issue engine
packages/do/src/issue-explainer.ts, packages/do/src/index.ts
Validates and grounds issue inputs, builds bounded prompts, invokes Workers AI when available, and returns structured degraded or successful results.
Explain issue admin RPC
packages/do/src/introspect.ts, packages/do/src/shard-do.ts, packages/studio/src/lib/admin.ts
Defines and dispatches explainIssue, audits applicable outcomes, and updates the CDC cache key separator.
Issue detail explanation UI
packages/studio/src/features/issues/issues-panel.tsx, packages/studio/src/locales/en.ts
Refactors issue rows and adds suggested fixes, opt-in explanations, loading/error/degraded states, triage handling, and localized labels.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant IssuesPanel
  participant ShardDO
  participant ErrorsCatalog
  participant WorkersAI
  IssuesPanel->>ShardDO: Request issue explanation
  ShardDO->>ErrorsCatalog: Match canonical solution
  ErrorsCatalog-->>ShardDO: Grounded facts
  ShardDO->>WorkersAI: Generate explanation
  WorkersAI-->>ShardDO: Explanation or failure
  ShardDO-->>IssuesPanel: Return explanation result
Loading

Possibly related PRs

  • anolilab/lunora#154: Extends the same issue triage workflow and admin/RPC-driven Studio issues UI that this change builds on.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: grounded and AI-powered issue explanations in Studio.
Description check ✅ Passed It covers the summary, package breakdown, and testing, with only non-critical template sections left blank.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/studio-issue-explainer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit bfd3eac.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
packages/do/src/shard-do.ts (2)

1511-1517: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound the inference call with a timeout.

binding.run is awaited with no deadline on a single-threaded DO's admin dispatch. handleExplainIssue already treats any throw as ai-error, so racing a timer degrades gracefully instead of letting a slow model hold the dispatch open.

♻️ Proposed guard
+/** Deadline for one explainer inference; a slow model degrades to the grounded hint. */
+const EXPLAIN_ISSUE_TIMEOUT_MS = 10_000;
+
...
-    const result = await binding.run(model, {
-        max_tokens: 400,
-        messages: [
-            { content: system, role: "system" },
-            { content: facts.join("\n"), role: "user" },
-        ],
-    });
+    const result = await Promise.race([
+        binding.run(model, {
+            max_tokens: 400,
+            messages: [
+                { content: system, role: "system" },
+                { content: facts.join("\n"), role: "user" },
+            ],
+        }),
+        new Promise<never>((_, reject) => {
+            setTimeout(() => reject(new Error("explainIssue: inference timed out")), EXPLAIN_ISSUE_TIMEOUT_MS);
+        }),
+    ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/shard-do.ts` around lines 1511 - 1517, Update the inference
call in handleExplainIssue to enforce a timeout when awaiting binding.run,
racing it against a timer that rejects once the deadline is reached. Preserve
the existing error propagation so timeout failures are handled as ai-error, and
ensure the timer is cleaned up when inference completes.

1438-1451: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

reason is typed string on both mirrored ExplainIssueResult declarations. The shared root cause is one over-wide field: the DO derives degraded(...)'s parameter from it, and the studio branches on a literal — neither is checked, so a typo'd sentinel compiles and silently falls into the generic error copy.

  • packages/do/src/shard-do.ts#L1438-L1451: narrow to reason?: "ai-error" | "empty-response" | "no-ai-binding" so degraded(reason) at Line 6801 rejects unknown values.
  • packages/studio/src/lib/admin.ts#L1155-L1188: apply the identical union so the reason === "no-ai-binding" branch in issues-panel.tsx is exhaustively checkable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/shard-do.ts` around lines 1438 - 1451, In
packages/do/src/shard-do.ts lines 1438-1451, narrow ExplainIssueResult.reason
from string to the union "ai-error" | "empty-response" | "no-ai-binding" so
degraded(reason) is type-checked. Apply the identical union to the mirrored
ExplainIssueResult declaration in packages/studio/src/lib/admin.ts lines
1155-1188 so the issues-panel.tsx literal branch remains exhaustively checked.
packages/studio/src/lib/admin.ts (1)

1155-1188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated wire contract — keep the two definitions in lockstep.

ExplainIssueArgs/ExplainIssueResult mirror @lunora/do by hand (intentionally, to avoid the dep edge), so nothing enforces agreement. Worth a note here pointing at the DO declaration, or a shared-types module if one already spans the two packages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/studio/src/lib/admin.ts` around lines 1155 - 1188, The
hand-maintained ExplainIssueArgs and ExplainIssueResult contracts in admin.ts
can drift from their `@lunora/do` counterparts. Add a concise maintenance note
referencing the corresponding Durable Object declarations, or reuse an existing
shared-types module if one already spans both packages, while preserving the
dependency boundary and keeping both definitions aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/do/src/shard-do.ts`:
- Line 1403: Update DEFAULT_EXPLAIN_ISSUE_MODEL to a currently supported Workers
AI text-generation model, replacing the deprecated
`@cf/meta/llama-3.1-8b-instruct` value while preserving the existing
issue-explanation flow.
- Around line 1458-1474: Update parseExplainIssueArgs so title and culprit are
truncated to the same prompt-budget limit as sampleMessage before being
returned. Preserve their existing trimming and undefined behavior, and ensure
runExplainIssueModel receives capped values for all user-provided prompt fields.

In `@packages/errors/src/catalog.ts`:
- Around line 509-552: Update findCloudflarePlatformSolution to prioritize
explicit “error <code>” and “error: <code>” matches before applying the weaker
cloudflare-plus-number heuristic. Use a strong-match pass that returns the
corresponding CLOUDFLARE_PLATFORM_ERRORS entry first, then retain the existing
standalone-number matching for cases without an explicit error code.

In `@packages/studio/src/features/issues/issues-panel.tsx`:
- Around line 113-137: Track the sample message associated with each explanation
and invalidate both the explanation and its association whenever
issue.sampleMessage changes, alongside groundedHint. In onExplain, capture the
current sampleMessage and ignore late query results if it no longer matches the
issue’s current message; render the AI explanation only when explainedFor equals
issue.sampleMessage.

---

Nitpick comments:
In `@packages/do/src/shard-do.ts`:
- Around line 1511-1517: Update the inference call in handleExplainIssue to
enforce a timeout when awaiting binding.run, racing it against a timer that
rejects once the deadline is reached. Preserve the existing error propagation so
timeout failures are handled as ai-error, and ensure the timer is cleaned up
when inference completes.
- Around line 1438-1451: In packages/do/src/shard-do.ts lines 1438-1451, narrow
ExplainIssueResult.reason from string to the union "ai-error" | "empty-response"
| "no-ai-binding" so degraded(reason) is type-checked. Apply the identical union
to the mirrored ExplainIssueResult declaration in
packages/studio/src/lib/admin.ts lines 1155-1188 so the issues-panel.tsx literal
branch remains exhaustively checked.

In `@packages/studio/src/lib/admin.ts`:
- Around line 1155-1188: The hand-maintained ExplainIssueArgs and
ExplainIssueResult contracts in admin.ts can drift from their `@lunora/do`
counterparts. Add a concise maintenance note referencing the corresponding
Durable Object declarations, or reuse an existing shared-types module if one
already spans both packages, while preserving the dependency boundary and
keeping both definitions aligned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f76678e3-cd2d-4633-b980-bbb922415505

📥 Commits

Reviewing files that changed from the base of the PR and between a0914c8 and 65b02c9.

⛔ Files ignored due to path filters (3)
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/errors/__tests__/errors.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/studio/__tests__/features/issues/issues-panel.test.tsx is excluded by !**/__tests__/** and included by packages/**
📒 Files selected for processing (7)
  • packages/do/src/introspect.ts
  • packages/do/src/shard-do.ts
  • packages/errors/src/catalog.ts
  • packages/errors/src/index.ts
  • packages/studio/src/features/issues/issues-panel.tsx
  • packages/studio/src/lib/admin.ts
  • packages/studio/src/locales/en.ts

Comment thread packages/do/src/shard-do.ts Outdated
Comment thread packages/do/src/shard-do.ts Outdated
Comment thread packages/errors/src/catalog.ts
Comment thread packages/studio/src/features/issues/issues-panel.tsx Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 27, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 15.74%

⚡ 2 improved benchmarks
✅ 249 untouched benchmarks
⏩ 2 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
in-batch: single IN(...) query + id->doc re-projection 952.2 µs 793.8 µs +19.95%
flat 3 primitives (the notify.send attribute shape) 62.1 µs 55.6 µs +11.68%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/studio-issue-explainer (bfd3eac) with alpha (cb576e3)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on alpha (a8532e8) during the generation of this report, so cb576e3 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Resolves every review comment on PR #215.

@lunora/errors
- findCloudflarePlatformSolution now matches in two passes, explicit
  `Error <code>` phrasing before the weaker cloudflare-plus-number
  heuristic, so a weak match on an earlier table entry can no longer beat
  an explicit match on a later one (`"Cloudflare Error 1102: exceeded
  after 524 ms"` resolved to 524, grounding the explainer in the wrong fix).
- Add a static regex pre-filter so a message mentioning neither "error" nor
  "cloudflare" skips both the lowercasing and the per-entry scans. This
  restores the CodSpeed `unmatched message (worst case)` bench to its alpha
  baseline (normalized ratio .42 vs alpha .42, up from .28 at PR head).

@lunora/do
- Replace the deprecated `@cf/meta/llama-3.1-8b-instruct` default with
  `@cf/meta/llama-3.3-70b-instruct-fp8-fast` (retired May 2026; a dead
  model-id would degrade every explain to `ai-error`).
- Cap `title`/`culprit` at 200 chars — they rode the same prompt as the
  message but bypassed the budget cap.
- Bound the inference call with a 10s deadline so a hung model can't hold
  the DO's admin dispatch open.
- Narrow `ExplainIssueResult["reason"]` to a closed union.

@lunora/studio
- Drop the redundant `useMemo` (React Compiler already caches it).
- Track the sample message an explanation was grounded in, so a live
  re-fold under an unchanged hash can't leave stale AI text on screen.
- Mirror the narrowed `reason` union and note the hand-mirrored contract.

Also regenerates the API snapshots, which the branch had not updated after
exporting `CLOUDFLARE_PLATFORM_ERRORS` / `CloudflarePlatformError`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bug/security pass
- `findCloudflarePlatformSolution` pass 1 matched a code as a prefix of a
  longer number: `"Error 10061: connect ECONNREFUSED"` (WSAECONNREFUSED)
  resolved to CF 1006, "your IP has been banned" — and that wrong hint
  became the explainer prompt's grounding facts. Pass 1 now applies the
  same digit-boundary check pass 2 already had.
- The platform table was folded into `findSolutionByMessage`, which sits on
  `resolveHint` and therefore `toErrorBody`. Most catalog codes carry no
  `hint`, so an ordinary `BAD_REQUEST` whose message merely mentioned
  "cloudflare" near a number shipped operator-only zone guidance ("review
  the zone's Firewall/WAF and IP Access Rules") to unauthenticated
  browsers. It also put the table on the CLI renderer and Vite overlay.
  Split out `findIssueSolution`, which the Studio and the explainer opt
  into; the wire path stays Lunora-only. This also removes the CodSpeed
  regression at its root (normalized ratio .423 vs alpha .421).
- Fence the untrusted error report in the model prompt and tell the system
  prompt it is data, not instructions. `sampleMessage` is any text a
  request can make a function throw, and it was interpolated with the same
  `Label: value` shape as the grounded section.
- An unmatched message still called the model with no grounding, and the
  result rendered identically to a catalog-backed one. The client now
  flags an explanation with no `groundedId`.
- Audit every invocation that reached the binding, not only successes —
  `ai-error`/`empty-response` are the billed calls worth recording.
- Cap the `model` override, the one caller-supplied field that was not.

Code-quality pass
- Extract `packages/do/src/issue-explainer.ts` (191 lines out of the
  9.6k-line `shard-do.ts`), following the `sql-console.ts` precedent;
  `handleExplainIssue` is now a thin adapter supplying `env.AI`.
- Extract `IssueDetail` from `IssueRow`, keyed on the sample message.
  This replaces the `explainedFor` bookkeeping and also fixes what that
  missed: a stale explain error and an in-flight busy flag survived a
  re-fold. The component stays mounted while collapsed so collapsing does
  not discard an inference already paid for.
- `ExplainIssueResult` is a discriminated union with named arms, so a
  degraded result can no longer type-check without a `reason` (which the
  client silently rendered as the generic AI-error copy).
- Drop the `hint` body from the wire — nothing read it; the client derives
  it from the same catalog offline. `groundedId` stays and now drives the
  ungrounded caveat.
- The DO test imports the real result type instead of a third hand-mirror
  that had widened `reason` back to `string`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/do/src/issue-explainer.ts`:
- Line 171: Update the issue explanation flow around sampleMessage and
findIssueSolution so catalog lookup receives the full canonical issue message,
while EXPLAIN_ISSUE_MESSAGE_CAP is applied only to the prompt/model input
payload. Preserve the existing truncated field for model context without
truncating the value used for local solution grounding.
- Line 207: Escape or otherwise neutralize occurrences of UNTRUSTED_FENCE in
caller-controlled report content before constructing facts in issue explanation
generation. Update the report-building logic associated with title, culprit, and
sampleMessage so embedded delimiters cannot close the fence or create prompt
instructions, while preserving the existing report content and fixed fence
format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ea00918c-6ea1-4468-899b-b84996e8144f

📥 Commits

Reviewing files that changed from the base of the PR and between c5b1441 and e777f31.

⛔ Files ignored due to path filters (6)
  • api-snapshots/do.api.md is excluded by none and included by none
  • api-snapshots/errors.api.md is excluded by none and included by none
  • api-snapshots/lunora.api.md is excluded by none and included by none
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/errors/__tests__/errors.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/studio/__tests__/features/issues/issues-panel.test.tsx is excluded by !**/__tests__/** and included by packages/**
📒 Files selected for processing (8)
  • packages/do/src/index.ts
  • packages/do/src/issue-explainer.ts
  • packages/do/src/shard-do.ts
  • packages/errors/src/catalog.ts
  • packages/errors/src/index.ts
  • packages/studio/src/features/issues/issues-panel.tsx
  • packages/studio/src/lib/admin.ts
  • packages/studio/src/locales/en.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/errors/src/index.ts
  • packages/studio/src/locales/en.ts
  • packages/studio/src/lib/admin.ts
  • packages/studio/src/features/issues/issues-panel.tsx

culprit: context(args["culprit"]),
model,
// Cap defensively so a runaway message can't inflate the model prompt.
sampleMessage: sampleMessage.slice(0, EXPLAIN_ISSUE_MESSAGE_CAP),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep catalog grounding independent of the prompt cap.

Studio resolves findIssueSolution(issue.sampleMessage) on the full message, but this path resolves after truncation. A Cloudflare code beyond 2,000 characters produces an ungrounded server response despite the UI showing a known fix. Preserve the canonical message for local solution lookup; apply the cap only when constructing model input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/issue-explainer.ts` at line 171, Update the issue explanation
flow around sampleMessage and findIssueSolution so catalog lookup receives the
full canonical issue message, while EXPLAIN_ISSUE_MESSAGE_CAP is applied only to
the prompt/model input payload. Preserve the existing truncated field for model
context without truncating the value used for local solution grounding.


report.push(`Error message: ${issue.sampleMessage}`);

const facts = [UNTRUSTED_FENCE, report.join("\n"), UNTRUSTED_FENCE];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Escape caller-controlled fence delimiters.

The fixed delimiter can be embedded in title, culprit, or sampleMessage; the cap does not prevent that. This lets untrusted text forge the closing boundary and appear as prompt instructions outside the fenced report.

Proposed fix
-    const facts = [UNTRUSTED_FENCE, report.join("\n"), UNTRUSTED_FENCE];
+    const fencedReport = report.join("\n").replaceAll(UNTRUSTED_FENCE, "[untrusted fence omitted]");
+    const facts = [UNTRUSTED_FENCE, fencedReport, UNTRUSTED_FENCE];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const facts = [UNTRUSTED_FENCE, report.join("\n"), UNTRUSTED_FENCE];
const fencedReport = report.join("\n").replaceAll(UNTRUSTED_FENCE, "[untrusted fence omitted]");
const facts = [UNTRUSTED_FENCE, fencedReport, UNTRUSTED_FENCE];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/issue-explainer.ts` at line 207, Escape or otherwise
neutralize occurrences of UNTRUSTED_FENCE in caller-controlled report content
before constructing facts in issue explanation generation. Update the
report-building logic associated with title, culprit, and sampleMessage so
embedded delimiters cannot close the fence or create prompt instructions, while
preserving the existing report content and fixed fence format.

@prisis
prisis merged commit ebf0ec7 into alpha Jul 28, 2026
42 of 44 checks passed
@prisis
prisis deleted the feat/studio-issue-explainer branch July 28, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant