Replace raw GenVM JSON errors with plain-English messages#1612
Replace raw GenVM JSON errors with plain-English messages#1612Dark-Brain07 wants to merge 2 commits intogenlayerlabs:mainfrom
Conversation
Resolves genlayerlabs#1609 ## Problem When new developers make mistakes (e.g., forgetting the runner comment on line 1), GenLayer Studio shows raw GenVM JSON error dumps that are intimidating and unhelpful. This is the genlayerlabs#1 onboarding blocker for new contributors reported in issue genlayerlabs#1609. ## Solution Introduced a GenVM error parser layer that intercepts raw error output and translates it into plain-English messages with actionable fix suggestions, while keeping the raw output accessible behind a collapsible 'Technical Details' toggle. ### New files - rontend/src/utils/genvmErrors.ts - Error parser with pattern matching for known GenVM error codes (absent_runner_comment, invalid_contract, VM_ERROR, InsufficientFundsError, etc.) - rontend/src/components/Simulator/GenVMErrorDisplay.vue - Polished error display component with title, reason, fix suggestion, and collapsible raw error with copy-to-clipboard - rontend/test/unit/utils/genvmErrors.test.ts - 18 unit tests covering all known patterns and edge cases ### Modified files - useContractQueries.ts - Deploy, write, and schema errors now surface friendly messages via parseGenvmError() - TransactionItem.vue - Error details in tx modal use GenVMErrorDisplay instead of raw pre blocks - ConstructorParameters.vue - Schema errors show GenVMErrorDisplay with actionable fix guidance
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 37 minutes and 47 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR implements a user-friendly GenVM error handling system that translates raw JSON error outputs into plain-English messages. It introduces a new error parsing utility, a dedicated error display component, and integrates both into existing hooks and components to replace raw error rendering with structured, actionable guidance and collapsible technical details. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
frontend/src/hooks/useContractQueries.ts (1)
180-191: Notification path looks good; minor: avoid double-parse work by reusingfriendly.rawError.
friendlyalready carriesrawError, so if you adopt the fix above and attach it on the thrown Error, you don't need to duplicate therawMsgcalculation across call sites. Also consider usingfinallyto resetisDeploying:♻️ Optional polish
- } catch (error: any) { - isDeploying.value = false; - const rawMsg = error?.details || error?.message || String(error); - const friendly = parseGenvmError(rawMsg); - notify({ - type: 'error', - title: friendly.title, - text: friendly.reason, - }); - console.error('Error Deploying the contract', error); - throw new Error(friendly.title + ': ' + friendly.reason); - } + } catch (error: any) { + const rawMsg = error?.details || error?.message || String(error); + const friendly = parseGenvmError(rawMsg); + notify({ type: 'error', title: friendly.title, text: friendly.reason }); + console.error('Error Deploying the contract', error); + const wrapped = new Error(`${friendly.title}: ${friendly.reason}`); + (wrapped as any).rawGenvmError = rawMsg; + throw wrapped; + } finally { + isDeploying.value = false; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/hooks/useContractQueries.ts` around lines 180 - 191, Reuse the parsed raw error from parseGenvmError instead of recomputing rawMsg: call parseGenvmError(rawMsg) once, then use friendly.rawError when constructing/logging/throwing the error (attach it to the thrown Error or include in the Error message) to avoid duplicate parsing; also move isDeploying.value = false into a finally block around the try/catch so isDeploying is always reset. Reference: the isDeploying flag, parseGenvmError result object (friendly), notify call and the thrown Error in this catch block.frontend/test/unit/utils/genvmErrors.test.ts (1)
8-80: Add a test for the realistic nested JSON-RPC error shape.The most common real input is a wrapped JSON-RPC error where the outer
"message"is generic and the actual GenVM code is nested deeper. Worth pinning down the contract:it('matches known code inside a nested JSON-RPC envelope', () => { const raw = JSON.stringify({ error: { message: 'Internal error', data: { result: { message: 'invalid_contract absent_runner_comment' } }, }, }); expect(parseGenvmError(raw).title).toBe('Missing runner comment'); });This would also guard against future changes to
extractErrorCodethat prefer the outermessage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/test/unit/utils/genvmErrors.test.ts` around lines 8 - 80, Add a unit test that verifies parseGenvmError correctly extracts a nested GenVM code inside a JSON-RPC envelope by creating a raw stringified payload where error.data.result.message contains "invalid_contract absent_runner_comment" and asserting parseGenvmError(raw).title === 'Missing runner comment'; this ensures parseGenvmError (and any helper like extractErrorCode) looks into error.data.result.message and not only the outer error.message.frontend/src/utils/genvmErrors.ts (2)
89-95:'timeout'pattern is too broad and will swallow unrelated errors.A bare
.includes('timeout')will match"ReadTimeoutError","socket timeout", LLM-provider connection timeouts, etc. — none of which are GenVM request timeouts and giving the user a "validators are slow to respond" fix is misleading.Tighten to a more specific marker (e.g. word-boundary regex or an explicit code like
request_timeout/Request timeout):♻️ Proposed refactor
- { - pattern: 'timeout', + { + pattern: /\b(request[_ ]timeout|Request timed out)\b/i, title: 'Request timed out',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/genvmErrors.ts` around lines 89 - 95, The current error entry with pattern: 'timeout' is too broad and will match unrelated timeouts; update the GenVM error matcher entry (the object with pattern: 'timeout' in genvmErrors.ts) to use a stricter pattern such as a word-boundary regex (e.g. /\btimeout\b/i) or explicit tokens like 'Request timeout' or 'request_timeout' so it only catches GenVM request timeouts, and adjust the associated title/reason/fix text to reflect validator request timeouts specifically (mention validators and retry guidance) rather than generic network/LLM provider timeouts.
104-120:extractErrorCodemay pick the wrong"message"from nested JSON-RPC payloads.Real GenVM errors typically come wrapped as JSON-RPC, e.g.:
{"error":{"message":"Internal error","data":{"result":{"message":"invalid_contract absent_runner_comment"}}}}The current regex returns the first
"message"match ("Internal error"), which doesn't match any entry inERROR_MAP. The loop inparseGenvmErrorthen falls back to a full-stringrawError.includes(pattern), which still works for known codes embedded in the dump — buterrorCodeitself is misleading, and any future tightening that preferserrorCodeover the full string would silently regress recognition.Consider preferring the innermost
message/kind, or collecting all candidates and matching any of them:♻️ Proposed refactor
-function extractErrorCode(rawError: string): string | null { - // Try to find the "message" field inside the result JSON - const messageMatch = rawError.match( - /"message"\s*:\s*"([^"]+)"/, - ); - if (messageMatch) { - return messageMatch[1]; - } - - // Try to find a "kind" field - const kindMatch = rawError.match(/"kind"\s*:\s*"([^"]+)"/); - if (kindMatch) { - return kindMatch[1]; - } - - return null; -} +function extractErrorCodes(rawError: string): string[] { + const codes: string[] = []; + const re = /"(?:message|kind)"\s*:\s*"((?:[^"\\]|\\.)*)"/g; + let m: RegExpExecArray | null; + while ((m = re.exec(rawError)) !== null) codes.push(m[1]); + return codes; +}…and then iterate
extractErrorCodes(rawError)(innermost last) when matching.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/utils/genvmErrors.ts` around lines 104 - 120, The current extractErrorCode function returns only the first "message" or "kind" it finds and thus often picks an outer JSON-RPC message; replace it with extractErrorCodes(rawError): string[] that extracts all "message" and "kind" values (try a safe JSON.parse of the payload when possible, otherwise use global regexes to collect all matches) and order candidates so innermost messages are preferred (e.g., collect matches and reverse them or place innermost last). Update parseGenvmError to iterate over the returned candidates from extractErrorCodes (checking each against ERROR_MAP) before falling back to substring matching of rawError. Ensure function names referenced: extractErrorCodes and parseGenvmError and that ERROR_MAP matching logic uses any candidate, preserving existing fallback behavior.frontend/src/components/Simulator/TransactionItem.vue (1)
615-619: ComputeextractErrorText(...)once per row to avoid double work and the!non-null assertion.
extractErrorTextis invoked twice per leader/validator row — once inv-if, once for the prop — and each call re-runsresultToUserFriendlyJsondecoding. With many validators this is wasteful; it also forces the!assertion. Extracting into a small helper component (or computing into the loop variable) cleans both up:♻️ Proposed refactor (template-only sketch)
- <GenVMErrorDisplay - v-if="extractErrorText(history.leader_result[0])" - :raw-error="extractErrorText(history.leader_result[0])!" - class="ml-5 mt-1" - /> + <template v-if="extractErrorText(history.leader_result[0]) as string | null"> + <GenVMErrorDisplay + v-if="extractErrorText(history.leader_result[0])" + :raw-error="extractErrorText(history.leader_result[0]) as string" + class="ml-5 mt-1" + /> + </template>A cleaner option: precompute
leaderErrors/validatorErrorsarrays (mapped fromconsensus_history) in<script setup>so each row already has itserrorTextfield.Also applies to: 680-684
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Simulator/TransactionItem.vue` around lines 615 - 619, The template calls extractErrorText(history.leader_result[0]) twice causing repeated work and requiring a non-null assertion; compute the error once and pass it to GenVMErrorDisplay instead. Update the row rendering to call extractErrorText(...) a single time (e.g., map consensus_history to include an errorText field in your <script setup> or create a tiny helper component that runs extractErrorText once) and then use v-if="row.errorText" and :raw-error="row.errorText" for the GenVMErrorDisplay; ensure you stop using the `!` assertion and rely on the precomputed nullable value so resultToUserFriendlyJson is only executed once per leader/validator.frontend/src/components/Simulator/GenVMErrorDisplay.vue (1)
81-90: Addtype="button"and basic ARIA to the disclosure toggle.Without
type="button", this would submit any enclosing<form>if the component is ever embedded in one. Addingaria-expanded/aria-controlsalso makes the collapsible region accessible to screen readers.♿ Proposed tweak
<button + type="button" + :aria-expanded="showRawDetails" + aria-controls="genvm-error-raw-details" class="flex w-full items-center gap-1.5 px-3 py-2 ..." data-testid="genvm-error-toggle-details" `@click`="showRawDetails = !showRawDetails" > @@ - <div v-if="showRawDetails" class="px-3 pb-3"> + <div v-if="showRawDetails" id="genvm-error-raw-details" class="px-3 pb-3">Same for the copy button at line 99 (
type="button").🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/components/Simulator/GenVMErrorDisplay.vue` around lines 81 - 90, The toggle button (data-testid="genvm-error-toggle-details", uses showRawDetails, ChevronDown/ChevronUp) should be a non-submitting button and accessible: add type="button", bind aria-expanded (e.g. :aria-expanded="showRawDetails") and add aria-controls pointing to the collapsible region id (create an id like "genvm-error-details" on the details container). Also update the copy button to include type="button" to avoid accidental form submission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/components/Simulator/ConstructorParameters.vue`:
- Around line 25-28: schemaErrorMessage currently returns the already-humanized
Error.message which breaks GenVM error parsing; update the computed to prefer
the original raw GenVM JSON when present by checking
schemaError.value.rawGenvmError (or a similar property set by
fetchContractSchema/useContractQueries.ts) and return that raw payload for
GenVMErrorDisplay/parseGenvmError to parse, falling back to
schemaError.value.message or String(schemaError.value) only if rawGenvmError is
absent.
In `@frontend/src/hooks/useContractQueries.ts`:
- Around line 116-120: The try/catch is replacing the original GenVM JSON with a
friendly string (throwing new Error(friendly.title + ': ' + friendly.reason))
which prevents downstream code (ConstructorParameters.vue ->
schemaError.value.message -> GenVMErrorDisplay) from re-parsing the raw JSON;
change the catch in useContractQueries (and mirror the same change in
deployContract and callWriteMethod) to throw an Error that still contains the
friendly message but also preserves the original rawMsg (attach rawMsg as a
property on the thrown Error object, e.g. err.rawMsg or err.originalRaw) so
parseGenvmError can be re-run by the UI; keep using parseGenvmError for the
friendly message but ensure the thrown Error carries both representations.
---
Nitpick comments:
In `@frontend/src/components/Simulator/GenVMErrorDisplay.vue`:
- Around line 81-90: The toggle button
(data-testid="genvm-error-toggle-details", uses showRawDetails,
ChevronDown/ChevronUp) should be a non-submitting button and accessible: add
type="button", bind aria-expanded (e.g. :aria-expanded="showRawDetails") and add
aria-controls pointing to the collapsible region id (create an id like
"genvm-error-details" on the details container). Also update the copy button to
include type="button" to avoid accidental form submission.
In `@frontend/src/components/Simulator/TransactionItem.vue`:
- Around line 615-619: The template calls
extractErrorText(history.leader_result[0]) twice causing repeated work and
requiring a non-null assertion; compute the error once and pass it to
GenVMErrorDisplay instead. Update the row rendering to call
extractErrorText(...) a single time (e.g., map consensus_history to include an
errorText field in your <script setup> or create a tiny helper component that
runs extractErrorText once) and then use v-if="row.errorText" and
:raw-error="row.errorText" for the GenVMErrorDisplay; ensure you stop using the
`!` assertion and rely on the precomputed nullable value so
resultToUserFriendlyJson is only executed once per leader/validator.
In `@frontend/src/hooks/useContractQueries.ts`:
- Around line 180-191: Reuse the parsed raw error from parseGenvmError instead
of recomputing rawMsg: call parseGenvmError(rawMsg) once, then use
friendly.rawError when constructing/logging/throwing the error (attach it to the
thrown Error or include in the Error message) to avoid duplicate parsing; also
move isDeploying.value = false into a finally block around the try/catch so
isDeploying is always reset. Reference: the isDeploying flag, parseGenvmError
result object (friendly), notify call and the thrown Error in this catch block.
In `@frontend/src/utils/genvmErrors.ts`:
- Around line 89-95: The current error entry with pattern: 'timeout' is too
broad and will match unrelated timeouts; update the GenVM error matcher entry
(the object with pattern: 'timeout' in genvmErrors.ts) to use a stricter pattern
such as a word-boundary regex (e.g. /\btimeout\b/i) or explicit tokens like
'Request timeout' or 'request_timeout' so it only catches GenVM request
timeouts, and adjust the associated title/reason/fix text to reflect validator
request timeouts specifically (mention validators and retry guidance) rather
than generic network/LLM provider timeouts.
- Around line 104-120: The current extractErrorCode function returns only the
first "message" or "kind" it finds and thus often picks an outer JSON-RPC
message; replace it with extractErrorCodes(rawError): string[] that extracts all
"message" and "kind" values (try a safe JSON.parse of the payload when possible,
otherwise use global regexes to collect all matches) and order candidates so
innermost messages are preferred (e.g., collect matches and reverse them or
place innermost last). Update parseGenvmError to iterate over the returned
candidates from extractErrorCodes (checking each against ERROR_MAP) before
falling back to substring matching of rawError. Ensure function names
referenced: extractErrorCodes and parseGenvmError and that ERROR_MAP matching
logic uses any candidate, preserving existing fallback behavior.
In `@frontend/test/unit/utils/genvmErrors.test.ts`:
- Around line 8-80: Add a unit test that verifies parseGenvmError correctly
extracts a nested GenVM code inside a JSON-RPC envelope by creating a raw
stringified payload where error.data.result.message contains "invalid_contract
absent_runner_comment" and asserting parseGenvmError(raw).title === 'Missing
runner comment'; this ensures parseGenvmError (and any helper like
extractErrorCode) looks into error.data.result.message and not only the outer
error.message.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: e8911cad-19ac-4931-a8be-ebf4851958ef
📒 Files selected for processing (6)
frontend/src/components/Simulator/ConstructorParameters.vuefrontend/src/components/Simulator/GenVMErrorDisplay.vuefrontend/src/components/Simulator/TransactionItem.vuefrontend/src/hooks/useContractQueries.tsfrontend/src/utils/genvmErrors.tsfrontend/test/unit/utils/genvmErrors.test.ts
Addresses CodeRabbit review feedback. The catch blocks in useContractQueries now attach the original raw error string as awGenvmError on the thrown Error, so ConstructorParameters.vue can pass the actual GenVM dump to GenVMErrorDisplay instead of the already-translated friendly text. Without this, GenVMErrorDisplay would re-parse 'Missing runner comment: Your contract is missing...' — none of the ERROR_MAP patterns match that, so every known error fell through to the generic 'Something went wrong' card. Now the raw dump is preserved and the component renders the correct friendly message with the original JSON behind Technical Details.
|
Good catch @coderabbitai — fixed in 358d92d. The thrown Error now carries TypeScript still compiles clean, all 18 unit tests still pass. |
|
Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit. |
Closes #1609
What this does
Right now, when a new dev forgets the runner comment (or makes any GenVM-related mistake), the Studio hits them with a wall of raw JSON that looks something like this:
Not great. The actual useful info (
absent_runner_comment) is buried 3 levels deep in the JSON.This PR adds an error parser that catches these raw dumps and translates them into something people can actually act on:
Before: Intimidating JSON blob → developer leaves and asks in Discord
After: "Missing runner comment → add
# { "Depends": "py-genlayer:test" }as line 1" → developer fixes it themselvesChanges
New files
frontend/src/utils/genvmErrors.ts— The parser. Maps known error codes (absent_runner_comment,invalid_contract,VM_ERROR,InsufficientFundsError, etc.) to human messages with fix suggestions. Unknown errors get a sensible fallback instead of just crashing.frontend/src/components/Simulator/GenVMErrorDisplay.vue— Drop-in component that renders the friendly error. Shows the title + reason + a "How to fix" box. Raw JSON lives behind a collapsible "Technical Details" toggle with a copy button (for bug reports).frontend/test/unit/utils/genvmErrors.test.ts— 18 tests covering all mapped patterns + edge cases.Modified files
useContractQueries.ts—deployContract(),callWriteMethod(), andfetchContractSchema()now run errors throughparseGenvmError()before surfacing them. Notifications show the friendly title/reason instead of "Error deploying contract".TransactionItem.vue— The error detail blocks in the tx modal (leader errors, validator errors) now useGenVMErrorDisplayinstead of raw<pre>dumps.ConstructorParameters.vue— When the schema query fails, the error area now rendersGenVMErrorDisplaywith the parsed message instead of just "Could not load contract schema."How it works
The parser (
parseGenvmError) does two things:"message": "..."or"kind": "..."){ title, reason, fix, rawError }If nothing matches, you get a generic "Something went wrong" with instructions to expand the technical details for bug reporting. The raw output is always preserved.
Acceptance criteria from the issue
gen_getContractSchemaForCodeand deploy errors before renderingTesting
npx vitest run test/unit/utils/genvmErrors.test.ts)vue-tsc --noEmit— zero errors)Summary by CodeRabbit
Release Notes
New Features
Tests