Skip to content

Feat/grpc scripting#8754

Open
sharan-bruno wants to merge 4 commits into
usebruno:mainfrom
sharan-bruno:feat/grpc-scripting
Open

Feat/grpc scripting#8754
sharan-bruno wants to merge 4 commits into
usebruno:mainfrom
sharan-bruno:feat/grpc-scripting

Conversation

@sharan-bruno

@sharan-bruno sharan-bruno commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

JIRA

Description

gRPC requests now support scripts and tests via four lifecycle phases — beforeCallStart, beforeMessageSend, afterMessageReceive, afterCallEnd — each exposing a namespaced bru.grpc.* API (request.messages/metadata/message, response.messages/message/trailers/statusCode), plus the common bru.* helpers. Backed by a shared script-phase registry in @usebruno/common, wired through the JS runtime, QuickJS sandbox, and Electron gRPC handlers, with autocomplete for the Script/Tests tabs.

image

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added gRPC scripting phase support (before call, before message send, after message receive, after call end) with enhanced bru.grpc scripting in supported runtimes.
    • Added Scripts and Tests tabs for gRPC request/response, including improved test results and script error cards.
    • Made autocomplete protocol/phase-aware for both HTTP and gRPC editors.
  • Bug Fixes

    • Improved script and test error detection and indicators by using phase-based metadata.
    • Refreshed HTTP and gRPC script/test rendering and wiring for more consistent results display and focusing.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This change centralizes HTTP and gRPC scripting phases, adds gRPC script execution and APIs, updates serialization and runtimes, and makes editors, Redux state, request panes, response panes, and test displays phase-aware.

Changes

Phase-aware scripting

Layer / File(s) Summary
Phase registry and serialization
packages/bruno-common/..., packages/bruno-converters/..., packages/bruno-filestore/..., packages/bruno-lang/..., packages/bruno-schema*
Defines shared scripting phases and maps HTTP/gRPC scripts through collection formats, language conversion, and schemas.
Script runtime and gRPC API
packages/bruno-js/...
Adds phase-scoped bru.grpc APIs, shared runtime execution helpers, QuickJS bridges, and updated runtime tests.
gRPC execution lifecycle
packages/bruno-electron/src/ipc/network/grpc-event-handlers.js, packages/bruno-requests/src/grpc/grpc-client.js
Runs gRPC scripts across call, message, receive, completion, and test phases while emitting results and managing streaming context.
HTTP and inherited script execution
packages/bruno-electron/src/ipc/network/..., packages/bruno-cli/src/runner/...
Uses phase metadata for script inheritance, HTTP execution, metadata, events, and CLI runtime entry points.
Editors and autocomplete
packages/bruno-app/src/components/CodeEditor/..., packages/bruno-app/src/components/RequestPane/Script/..., packages/bruno-app/src/utils/codemirror/...
Passes request and script phase context into editors and generates HTTP/gRPC-specific hints.
Request and response UI state
packages/bruno-app/src/components/RequestPane/..., packages/bruno-app/src/components/ResponsePane/..., packages/bruno-app/src/providers/...
Adds gRPC script/tests tabs and derives script errors, test results, indicators, and Redux updates from phase metadata.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: helloanoop, bijin-bruno, sid-bruno

Poem

Phases line up, from call to stream,
Scripts awaken inside the dream.
Hints bloom bright in editors blue,
Tests and errors find their view.
gRPC messages dance in flight—
One shared map makes order right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding gRPC scripting support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-schema/src/collections/index.js (1)

517-553: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trailing comma on line 537.

afterCallEnd: Yup.string().nullable(), is followed directly by the closing }), leaving a trailing comma on a changed line.

🧹 Proposed fix
   script: Yup.object({
     beforeCallStart: Yup.string().nullable(),
     beforeMessageSend: Yup.string().nullable(),
     afterMessageReceive: Yup.string().nullable(),
-    afterCallEnd: Yup.string().nullable(),
+    afterCallEnd: Yup.string().nullable()
   })
As per coding guidelines, "Do not use trailing commas."
🤖 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/bruno-schema/src/collections/index.js` around lines 517 - 553,
Remove the trailing comma after the afterCallEnd property in the script schema
within grpcRequestSchema, leaving the closing object syntax unchanged.

Source: Coding guidelines

🧹 Nitpick comments (3)
packages/bruno-common/src/scripting/phases.ts (1)

41-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

deepFreeze doesn't surface immutability at the type level.

It returns T rather than a Readonly/DeepReadonly<T>, so TypeScript won't catch a consumer attempting to mutate SCRIPT_PHASES at compile time, even though the runtime freeze would silently no-op (or throw in strict mode).

♻️ Suggested typing
-const deepFreeze = <T>(obj: T): T => {
+type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> };
+
+const deepFreeze = <T>(obj: T): DeepReadonly<T> => {
   Object.values(obj as Record<string, unknown>).forEach((value) => {
     if (value && typeof value === 'object') {
       deepFreeze(value);
     }
   });
-  return Object.freeze(obj);
+  return Object.freeze(obj) as DeepReadonly<T>;
 };
🤖 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/bruno-common/src/scripting/phases.ts` around lines 41 - 48, Update
deepFreeze so its return type expresses recursive readonly immutability, using
the project’s existing DeepReadonly type if available or defining an equivalent
type near deepFreeze. Ensure SCRIPT_PHASES receives that deeply readonly type
while preserving the existing runtime freezing behavior.
packages/bruno-converters/src/opencollection/common/scripts.ts (1)

6-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Duplicate phase-iteration script (de)serialization logic across packages. Both files independently implement the same "loop getPhasesByRequestType, read/write script[phase.FIELD] against phase.YML_TYPE" logic; both already depend on @usebruno/common for the phase registry, making a shared helper there a natural consolidation point.

  • packages/bruno-converters/src/opencollection/common/scripts.ts#L6-L62: extract toOpenCollectionScripts/fromOpenCollectionScripts's phase-loop logic into a shared @usebruno/common helper and delegate here.
  • packages/bruno-filestore/src/formats/yml/common/scripts.ts#L6-L66: extract toOpenCollectionScripts/toBrunoScripts's equivalent phase-loop logic and delegate to the same shared helper.
🤖 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/bruno-converters/src/opencollection/common/scripts.ts` around lines
6 - 62, Extract the duplicated phase-based script serialization and
deserialization logic from toOpenCollectionScripts/fromOpenCollectionScripts in
packages/bruno-converters/src/opencollection/common/scripts.ts and the
equivalent toOpenCollectionScripts/toBrunoScripts logic in
packages/bruno-filestore/src/formats/yml/common/scripts.ts into shared helpers
in `@usebruno/common`. Make both files delegate to those helpers while preserving
request-type phase selection, phase.FIELD/YML_TYPE mapping, and tests handling.
packages/bruno-js/src/runtime/test-runtime.js (1)

57-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the shared SCRIPT_PHASES constant for the gRPC phase.

In packages/bruno-js/src/runtime/test-runtime.js, import SCRIPT_PHASES from @usebruno/common and replace 'afterCallEnd' with SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD so gRPC tests keep using the canonical phase field.

🤖 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/bruno-js/src/runtime/test-runtime.js` at line 57, Update the gRPC
phase assignment in the runtime test setup to import and use the shared
SCRIPT_PHASES constant from `@usebruno/common`, replacing the hardcoded
'afterCallEnd' value with SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD while
preserving the existing non-gRPC behavior.
🤖 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/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js`:
- Around line 150-152: Update the ScriptErrorIcon invocation in GrpcResponsePane
to pass itemUid={item.uid} instead of item, while preserving the existing
onClick behavior and conditional rendering.

In `@packages/bruno-common/src/scripting/phases.ts`:
- Around line 102-112: Update the ERROR_STATE_KEY value in the
AFTER_MESSAGE_RECEIVE phase configuration to include “Receive,” matching its
FIELD and TEST_RESULTS_KEY naming pattern while leaving the other phase metadata
unchanged.

In `@packages/bruno-electron/src/ipc/network/grpc-event-handlers.js`:
- Around line 562-597: Update the gRPC after-message lifecycle around
afterMessageReceive and afterCallEnd so receive scripts are serialized or
awaited before afterCallEnd begins. Preserve ordering of shared
envVars/runtimeVariables side effects and ensure afterCallEnd observes any
afterMessageReceive error before running.

In `@packages/bruno-js/src/runtime/script-runtime.js`:
- Around line 353-383: Update the beforeMessageSend flow around the phaseData
object and final return so the script result uses the message stored on the
phase object after sandbox execution, rather than the original outgoingMessage
parameter. Preserve the existing script result handling and return the mutated
phaseData.message value for downstream runBeforeMessageSend processing.

In `@packages/bruno-lang/v2/src/bruToJson.js`:
- Around line 1132-1135: Update the script semantic action to warn when
FIELD_BY_BRU_TYPE has no entry for scriptType.sourceString before returning the
existing empty object. Preserve the current mapped-script behavior, and include
the unrecognized script type in the warning so discarded script content is
visible to users.

In `@packages/bruno-lang/v2/src/jsonToBru.js`:
- Around line 737-749: Update the requestType assignment in the script
serialization block to detect gRPC using grpc && grpc.url, matching the existing
condition in the surrounding conversion logic. Ensure truthy grpc objects
without a URL are treated as HTTP so script.req and script.res phases remain
serialized.

---

Outside diff comments:
In `@packages/bruno-schema/src/collections/index.js`:
- Around line 517-553: Remove the trailing comma after the afterCallEnd property
in the script schema within grpcRequestSchema, leaving the closing object syntax
unchanged.

---

Nitpick comments:
In `@packages/bruno-common/src/scripting/phases.ts`:
- Around line 41-48: Update deepFreeze so its return type expresses recursive
readonly immutability, using the project’s existing DeepReadonly type if
available or defining an equivalent type near deepFreeze. Ensure SCRIPT_PHASES
receives that deeply readonly type while preserving the existing runtime
freezing behavior.

In `@packages/bruno-converters/src/opencollection/common/scripts.ts`:
- Around line 6-62: Extract the duplicated phase-based script serialization and
deserialization logic from toOpenCollectionScripts/fromOpenCollectionScripts in
packages/bruno-converters/src/opencollection/common/scripts.ts and the
equivalent toOpenCollectionScripts/toBrunoScripts logic in
packages/bruno-filestore/src/formats/yml/common/scripts.ts into shared helpers
in `@usebruno/common`. Make both files delegate to those helpers while preserving
request-type phase selection, phase.FIELD/YML_TYPE mapping, and tests handling.

In `@packages/bruno-js/src/runtime/test-runtime.js`:
- Line 57: Update the gRPC phase assignment in the runtime test setup to import
and use the shared SCRIPT_PHASES constant from `@usebruno/common`, replacing the
hardcoded 'afterCallEnd' value with SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD
while preserving the existing non-gRPC behavior.
🪄 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: ad46095d-b139-4547-b097-819679da7456

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1170b and 9f1f34c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (50)
  • packages/bruno-app/src/components/CodeEditor/index.js
  • packages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.js
  • packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
  • packages/bruno-app/src/components/RequestPane/Script/index.js
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ScriptError/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResults/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/utils/codemirror/autocomplete.js
  • packages/bruno-app/src/utils/network/index.js
  • packages/bruno-cli/src/runner/run-single-request.js
  • packages/bruno-common/src/index.ts
  • packages/bruno-common/src/scripting/phases.ts
  • packages/bruno-converters/src/opencollection/common/scripts.ts
  • packages/bruno-converters/src/opencollection/items/grpc.ts
  • packages/bruno-electron/src/ipc/network/grpc-event-handlers.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-electron/src/ipc/network/prepare-grpc-request.js
  • packages/bruno-electron/src/utils/collection.js
  • packages/bruno-filestore/src/formats/yml/common/scripts.ts
  • packages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.ts
  • packages/bruno-filestore/src/formats/yml/items/parseGrpcRequest.ts
  • packages/bruno-filestore/src/formats/yml/items/parseHttpRequest.ts
  • packages/bruno-filestore/src/formats/yml/items/parseWebsocketRequest.ts
  • packages/bruno-filestore/src/formats/yml/items/stringifyGrpcRequest.ts
  • packages/bruno-filestore/src/formats/yml/parseCollection.ts
  • packages/bruno-filestore/src/formats/yml/parseFolder.ts
  • packages/bruno-js/src/bru.js
  • packages/bruno-js/src/bruno-onmessage.js
  • packages/bruno-js/src/grpc-script-api.js
  • packages/bruno-js/src/runtime/assert-runtime.js
  • packages/bruno-js/src/runtime/script-runtime.js
  • packages/bruno-js/src/runtime/test-runtime.js
  • packages/bruno-js/src/runtime/vars-runtime.js
  • packages/bruno-js/src/sandbox/quickjs/shims/bru.js
  • packages/bruno-js/src/sandbox/quickjs/shims/bruno-grpc.js
  • packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js
  • packages/bruno-js/src/utils.js
  • packages/bruno-js/src/utils/error-formatter.js
  • packages/bruno-js/tests/runtime.spec.js
  • packages/bruno-js/tests/script-runtime-scripted-entries.spec.js
  • packages/bruno-lang/v2/src/bruToJson.js
  • packages/bruno-lang/v2/src/jsonToBru.js
  • packages/bruno-requests/src/grpc/grpc-client.js
  • packages/bruno-schema-types/src/common/scripts.ts
  • packages/bruno-schema/src/collections/index.js

Comment on lines +150 to +152
{hasScriptOrTestError && !showScriptErrorCard && (
<ScriptErrorIcon item={item} onClick={() => setShowScriptErrorCard(true)} />
)}

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'GrpcResponsePane|ScriptErrorIcon' . | sed 's#^\./##'

echo "== relevant GrpcResponsePane section =="
file="$(fd 'index.js' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '120,170p' "$file" | cat -n
fi

echo "== ScriptErrorIcon definitions/usages =="
rg -n "function ScriptErrorIcon|const ScriptErrorIcon|ScriptErrorIcon|itemUid|toolhintId|id=" packages/bruno-app/src -g '*.js' -g '*.jsx' -g '*.ts' -g '*.tsx'

Repository: usebruno/bruno

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate GrpcResponsePane and ScriptErrorIcon =="
find packages/bruno-app/src/components/ResponsePane \( -path '*/GrpcResponsePane/*' -o -path '*/ScriptErrorIcon/*' \) -type f | sort

echo "== ScriptErrorIcon =="
fi="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/ScriptErrorIcon | head -n1 || true)"
if [ -n "${fi:-}" ]; then
  wc -l "$fi"
  cat -n "$fi" | sed -n '1,180p'
fi

echo "== GrpcResponsePane imports/render around Icon =="
gf="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${gf:-}" ]; then
  rg -n "ScriptErrorIcon|hasScriptOrTestError|showScriptErrorCard|setShowScriptErrorCard|import ScriptErrorIcon|useIpc" "$gf" -A 6 -B 6
fi

Repository: usebruno/bruno

Length of output: 2634


🏁 Script executed:

#!/bin/bash
set -euo pipefail

gf="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${gf:-}" ]; then
  echo "== GrpcResponsePane =="
  wc -l "$gf"
  cat -n "$gf" | sed -n '1,220p'

  echo "== focused ScriptErrorIcon occurrences =="
  rg -n "ScriptErrorIcon|hasScriptOrTestError|showScriptErrorCard|itemUid" "$gf" -A 4 -B 4 || true

  echo "== sibling ScriptErrorIcon usages =="
  rg -n "ScriptErrorIcon" packages/bruno-app/src/components/ResponsePane packages/bruno-app/src/components/RunnerResults/ResponsePane -A 2 -B 2 || true
fi

echo "== read-only prop destructuring behavior =="
node - <<'JS'
function scriptErrorIconFromArgs(args) {
  const { itemUid, onClick, className } = args;
  return `script-error-icon-${itemUid}`;
}
console.log(JSON.stringify({
  itemProp: scriptErrorIconFromArgs({ item: { uid: 'req-1' } }),
  itemUidProp: scriptErrorIconFromArgs({ itemUid: 'req-1' })
}, null, 2));
JS

Repository: usebruno/bruno

Length of output: 5765


Pass itemUid into ScriptErrorIcon.

ScriptErrorIcon destructures itemUid, not item, so the tooltip anchor becomes script-error-icon-undefined. Match the other ResponsePane callers with itemUid={item.uid}.

🤖 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/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js`
around lines 150 - 152, Update the ScriptErrorIcon invocation in
GrpcResponsePane to pass itemUid={item.uid} instead of item, while preserving
the existing onClick behavior and conditional rendering.

Comment on lines +102 to +112
AFTER_MESSAGE_RECEIVE: {
REQUEST_TYPE: 'grpc-request',
FIELD: 'afterMessageReceive',
YML_TYPE: 'grpc:after-message-receive',
BRU_TYPE: 'grpc:after-message-receive',
SCRIPT_TYPE: 'grpc:after-message-receive',
LABEL: 'After Message',
HINTS: ['bru'],
ERROR_STATE_KEY: 'afterMessageScriptError',
TEST_RESULTS_KEY: 'afterMessageReceiveTestResults'
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

ERROR_STATE_KEY typo breaks the naming pattern for AFTER_MESSAGE_RECEIVE.

Every other GRPC phase's ERROR_STATE_KEY mirrors its FIELD (beforeCallStartScriptError, beforeMessageSendScriptError, afterCallEndScriptError), and this phase's own TEST_RESULTS_KEY correctly uses the full name (afterMessageReceiveTestResults). ERROR_STATE_KEY: 'afterMessageScriptError' drops "Receive", which looks like a typo.

🐛 Proposed fix
       LABEL: 'After Message',
       HINTS: ['bru'],
-      ERROR_STATE_KEY: 'afterMessageScriptError',
+      ERROR_STATE_KEY: 'afterMessageReceiveScriptError',
       TEST_RESULTS_KEY: 'afterMessageReceiveTestResults'
📝 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
AFTER_MESSAGE_RECEIVE: {
REQUEST_TYPE: 'grpc-request',
FIELD: 'afterMessageReceive',
YML_TYPE: 'grpc:after-message-receive',
BRU_TYPE: 'grpc:after-message-receive',
SCRIPT_TYPE: 'grpc:after-message-receive',
LABEL: 'After Message',
HINTS: ['bru'],
ERROR_STATE_KEY: 'afterMessageScriptError',
TEST_RESULTS_KEY: 'afterMessageReceiveTestResults'
},
AFTER_MESSAGE_RECEIVE: {
REQUEST_TYPE: 'grpc-request',
FIELD: 'afterMessageReceive',
YML_TYPE: 'grpc:after-message-receive',
BRU_TYPE: 'grpc:after-message-receive',
SCRIPT_TYPE: 'grpc:after-message-receive',
LABEL: 'After Message',
HINTS: ['bru'],
ERROR_STATE_KEY: 'afterMessageReceiveScriptError',
TEST_RESULTS_KEY: 'afterMessageReceiveTestResults'
},
🤖 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/bruno-common/src/scripting/phases.ts` around lines 102 - 112, Update
the ERROR_STATE_KEY value in the AFTER_MESSAGE_RECEIVE phase configuration to
include “Receive,” matching its FIELD and TEST_RESULTS_KEY naming pattern while
leaving the other phase metadata unchanged.

Comment on lines +562 to +597
let afterMessageReceiveErrored = false;

// ── After Message Receive ──────────────────────────────────────────────
const hasAfterMessageReceiveScript = !!get(preparedRequest, `script.${SCRIPT_PHASES.GRPC.AFTER_MESSAGE_RECEIVE.FIELD}`)?.length;
const afterMessageReceive = hasAfterMessageReceiveScript
? (message) => {
const messageReceivedAt = new Date();
runAfterMessageReceive({ ...scriptContext, message, messageReceivedAt })
.then(({ scriptError }) => {
if (scriptError) afterMessageReceiveErrored = true;
})
.catch((err) => {
console.error('Error running gRPC afterMessageReceive script:', err);
});
}
: undefined;

// ── After Call End ─────────────────────────────────────────────────────
const hasAfterCallEndScript = !!get(preparedRequest, `script.${SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD}`)?.length;
const testFile = get(preparedRequest, 'tests');
const hasTests = typeof testFile === 'string' && testFile.length > 0;
const afterCallEnd = (hasAfterCallEndScript || hasTests)
? (response = {}) => {
if (afterMessageReceiveErrored) return;
(async () => {
if (hasAfterCallEndScript) {
await runAfterCallEnd({ ...scriptContext, response });
}
if (hasTests) {
await runTestFile({ ...scriptContext, response });
}
})().catch((err) => {
console.error('Error running gRPC afterCallEnd script/tests:', err);
});
}
: undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how startConnection sequences onAfterMessageReceive vs onAfterCallEnd
fd -t f 'grpc-client.js' packages/bruno-requests --exec cat -n {}

Repository: usebruno/bruno

Length of output: 48809


🏁 Script executed:

#!/bin/bash
# Inspect the gRPC event handler implementation and relevant helper callers/definitions.
fd -t f 'grpc-event-handlers.js' packages/bruno-electron --exec sh -c '
  file="$1"
  echo "===== $file outline ====="
  ast-grep outline "$file" --match startGrpcOrRestRequest --view compact || true
  echo "===== lines 530-610 ====="
  sed -n "530,610p" "$file" | nl -ba -v530
' sh {}
echo "===== relevant references ====="
rg -n "runAfterMessageReceive|runAfterCallEnd|runTestFile|afterMessageReceiveErrored|afterMessageReceive|afterCallEnd|SCRIPT_PHASES\.GRPC|onAfterMessageReceive|onAfterCallEnd" packages/bruno-electron/src packages/bruno-requests/src -S

Repository: usebruno/bruno

Length of output: 8590


Serialize or await afterMessageReceive before afterCallEnd runs.

The gRPC client calls onAfterMessageReceive synchronously on data responses and on onAfterCallEnd when the call completes, but the IPC handler starts runAfterMessageReceive(...) as fire-and-forget and only stores its error via .then. If a receive script is async/await, afterCallEnd can start before that error is recorded and can also run before receive scripts’ side-effects on shared envVars/runtimeVariables are visible for the same response. Queue the receive handlers or await them before invoking afterCallEnd.

🤖 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/bruno-electron/src/ipc/network/grpc-event-handlers.js` around lines
562 - 597, Update the gRPC after-message lifecycle around afterMessageReceive
and afterCallEnd so receive scripts are serialized or awaited before
afterCallEnd begins. Preserve ordering of shared envVars/runtimeVariables side
effects and ensure afterCallEnd observes any afterMessageReceive error before
running.

Comment on lines +353 to +383
phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD,
phaseData: { message: outgoingMessage }
});

const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);

const context = {
bru,
test,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults,
__bruSetScope: createScopeSetter(bru)
};

const bruConsole = this.#buildConsole(onConsoleLog);
if (bruConsole) context.console = bruConsole;

bindRunRequest(bru, runRequestByItemPathname);

const buildRequestScriptResult = () => this.#buildScriptResult({
primary: { request },
bru,
testResults: __brunoTestResults,
envVariables,
runtimeVariables,
globalEnvironmentVariables
});

const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult);
return { ...result, message: outgoingMessage };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

beforeMessageSend edits are silently dropped.

phaseData: { message: outgoingMessage } is a fresh object. When the script mutates the message, GrpcMessage.set/update reassign phaseData.message — not the outgoingMessage parameter. The return then hands back the original outgoingMessage, so any edits are lost. Downstream, runBeforeMessageSend in grpc-event-handlers.js writes scriptResult.message to the wire, so the transformed payload never leaves the app.

Read the value back off the phase object:

🐛 Proposed fix
+    const phaseData = { message: outgoingMessage };
     const bru = new Bru({
       runtime: this.runtime,
       envVariables,
       runtimeVariables,
       processEnvVars,
       collectionPath,
       collectionVariables,
       folderVariables,
       requestVariables,
       globalEnvironmentVariables,
       oauth2CredentialVariables,
       collectionName,
       promptVariables,
       certsAndProxyConfig,
       requestUrl: request?.url,
       request,
       phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD,
-      phaseData: { message: outgoingMessage }
+      phaseData
     });
@@
     const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult);
-    return { ...result, message: outgoingMessage };
+    return { ...result, message: phaseData.message };
📝 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
phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD,
phaseData: { message: outgoingMessage }
});
const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
const context = {
bru,
test,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults,
__bruSetScope: createScopeSetter(bru)
};
const bruConsole = this.#buildConsole(onConsoleLog);
if (bruConsole) context.console = bruConsole;
bindRunRequest(bru, runRequestByItemPathname);
const buildRequestScriptResult = () => this.#buildScriptResult({
primary: { request },
bru,
testResults: __brunoTestResults,
envVariables,
runtimeVariables,
globalEnvironmentVariables
});
const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult);
return { ...result, message: outgoingMessage };
phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD,
phaseData
});
const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
const context = {
bru,
test,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults,
__bruSetScope: createScopeSetter(bru)
};
const bruConsole = this.#buildConsole(onConsoleLog);
if (bruConsole) context.console = bruConsole;
bindRunRequest(bru, runRequestByItemPathname);
const buildRequestScriptResult = () => this.#buildScriptResult({
primary: { request },
bru,
testResults: __brunoTestResults,
envVariables,
runtimeVariables,
globalEnvironmentVariables
});
const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult);
return { ...result, message: phaseData.message };
🤖 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/bruno-js/src/runtime/script-runtime.js` around lines 353 - 383,
Update the beforeMessageSend flow around the phaseData object and final return
so the script result uses the message stored on the phase object after sandbox
execution, rather than the original outgoingMessage parameter. Preserve the
existing script result handling and return the mutated phaseData.message value
for downstream runBeforeMessageSend processing.

Comment on lines +1132 to 1135
script(_1, scriptType, _2, _3, _4, textblock, _5) {
const field = FIELD_BY_BRU_TYPE[scriptType.sourceString];
return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {};
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Unrecognized script:* types now silently vanish instead of failing the parse.

Previously an unmatched script header failed the OHM grammar match (loud error). Now scripttype matches any letters/digits/hyphens/colons, and the semantic action quietly discards the block ({}) when FIELD_BY_BRU_TYPE has no entry — e.g. a typo'd phase name (script:pre-reqeust) will silently drop the whole script body with no error, instead of failing loudly as before. Consider at least logging a warning so users notice lost content.

🐛 Proposed fix: warn on unmatched script type
   script(_1, scriptType, _2, _3, _4, textblock, _5) {
     const field = FIELD_BY_BRU_TYPE[scriptType.sourceString];
+    if (!field) {
+      console.warn(`Unknown script type "${scriptType.sourceString}" — its content was ignored.`);
+    }
     return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {};
   },
📝 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
script(_1, scriptType, _2, _3, _4, textblock, _5) {
const field = FIELD_BY_BRU_TYPE[scriptType.sourceString];
return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {};
},
script(_1, scriptType, _2, _3, _4, textblock, _5) {
const field = FIELD_BY_BRU_TYPE[scriptType.sourceString];
if (!field) {
console.warn(`Unknown script type "${scriptType.sourceString}" — its content was ignored.`);
}
return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {};
},
🤖 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/bruno-lang/v2/src/bruToJson.js` around lines 1132 - 1135, Update the
script semantic action to warn when FIELD_BY_BRU_TYPE has no entry for
scriptType.sourceString before returning the existing empty object. Preserve the
current mapped-script behavior, and include the unrecognized script type in the
warning so discarded script content is visible to users.

Comment on lines +737 to 749
if (script) {
const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) {
const code = script[FIELD];
if (code && code.length) {
bru += `script:${BRU_TYPE} {
${indentString(code)}
}

`;
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align gRPC detection with the rest of the file (grpc && grpc.url).

requestType is derived from bare grpc truthiness, but every other gRPC branch in this same function (line 63: if (grpc && grpc.url)) treats a grpc object without a url as "not a grpc request." If json.grpc is ever a truthy-but-empty object, this mismatch would classify the item as GRPC and only serialize GRPC-phase scripts, silently dropping script.req/script.res.

🐛 Proposed fix
-    const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
+    const requestType = grpc?.url ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
📝 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
if (script) {
const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) {
const code = script[FIELD];
if (code && code.length) {
bru += `script:${BRU_TYPE} {
${indentString(code)}
}
`;
}
}
}
if (script) {
const requestType = grpc?.url ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) {
const code = script[FIELD];
if (code && code.length) {
bru += `script:${BRU_TYPE} {
${indentString(code)}
}
`;
}
}
}
🤖 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/bruno-lang/v2/src/jsonToBru.js` around lines 737 - 749, Update the
requestType assignment in the script serialization block to detect gRPC using
grpc && grpc.url, matching the existing condition in the surrounding conversion
logic. Ensure truthy grpc objects without a URL are treated as HTTP so
script.req and script.res phases remain serialized.

@sharan-bruno
sharan-bruno force-pushed the feat/grpc-scripting branch from 9f1f34c to 558ab40 Compare July 23, 2026 14:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-app/src/components/RequestPane/Script/index.js (1)

95-98: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

buildAiContextPayload is undefined — confirmed by lint failure.

The lint check flags 'buildAiContextPayload' is not defined at this line, which will throw a ReferenceError on every render of this component (it's called unconditionally in a top-level useMemo), crashing the Script tab for both HTTP and gRPC requests. It appears the import for this function is missing from the top of the file.

🐛 Add the missing import
+import { buildAiContextPayload } from 'utils/ai/buildAiContextPayload'; // adjust path to actual module

Please run a search to confirm the correct module path:

#!/bin/bash
rg -n 'buildAiContextPayload' --type=js -C2
🤖 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/bruno-app/src/components/RequestPane/Script/index.js` around lines
95 - 98, Import buildAiContextPayload from its existing module, first locating
the canonical export path with a repository search. Add the import alongside the
other dependencies in the Script component so the top-level useMemo can resolve
the function without changing its existing behavior.

Source: Linters/SAST tools

🤖 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/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 42-45: Update getDefaultTab to guard SCRIPT_PHASES[0] before
accessing its FIELD property, returning the existing fallback behavior when no
phases are registered. Preserve the current first-phase script selection when
SCRIPT_PHASES[0] exists.
- Around line 153-161: Update the error-state lookup used by the StatusDot in
the hasScript rendering block to construct the key from phase.ERROR_STATE_KEY
followed by “Message”, matching the pattern used by GrpcRequestPane. Preserve
the existing error/default StatusDot behavior while ensuring pre-request and
other script phases resolve their actual item error fields.
- Around line 72-79: Move the useFocusErrorLine invocations out of the
SCRIPT_PHASES.forEach loop in the Script component. Preserve focus behavior for
each script phase while complying with the Rules of Hooks by calling the hook at
a stable component level or delegating each phase to a child component with its
own hook call; continue passing the corresponding item.uid, editor ref, script
phase, and visibility state.

In `@packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js`:
- Line 70: Update hasScriptError and the related effect in ResponsePane to
derive script errors from getPhasesByRequestType(item?.type), while also
including the legacy test error. Keep both the error icon and error-card
behavior consistent with ScriptError so gRPC-only phase errors are detected.

---

Outside diff comments:
In `@packages/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 95-98: Import buildAiContextPayload from its existing module,
first locating the canonical export path with a repository search. Add the
import alongside the other dependencies in the Script component so the top-level
useMemo can resolve the function without changing its existing behavior.
🪄 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: ef1b9d56-c095-4213-a8ff-268751b15acb

📥 Commits

Reviewing files that changed from the base of the PR and between 9f1f34c and 558ab40.

📒 Files selected for processing (13)
  • packages/bruno-app/src/components/CodeEditor/index.js
  • packages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.js
  • packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
  • packages/bruno-app/src/components/RequestPane/Script/index.js
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/ScriptError/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResults/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/utils/codemirror/autocomplete.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js
  • packages/bruno-app/src/components/ResponsePane/TestResults/index.js
  • packages/bruno-app/src/components/CodeEditor/index.js
  • packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
  • packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
  • packages/bruno-app/src/components/ResponsePane/index.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.js
  • packages/bruno-app/src/utils/codemirror/autocomplete.js

Comment on lines 42 to 45
const getDefaultTab = () => {
const hasPreRequestScript = requestScript && requestScript.trim().length > 0;
return hasPreRequestScript ? 'pre-request' : 'post-response';
const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unguarded access to SCRIPT_PHASES[0] can throw if the array is empty.

getDefaultTab reads SCRIPT_PHASES[0].FIELD without an existence check, while the very next line safely uses SCRIPT_PHASES[1]?.SCRIPT_TYPE. If item?.type doesn't match any registered phase (e.g. getPhasesByRequestType returns []), this throws a TypeError on render.

🛡️ Guard against an empty phases array
   const getDefaultTab = () => {
-    const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
+    if (!SCRIPT_PHASES.length) return undefined;
+    const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
     return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE;
   };
📝 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 getDefaultTab = () => {
const hasPreRequestScript = requestScript && requestScript.trim().length > 0;
return hasPreRequestScript ? 'pre-request' : 'post-response';
const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE;
};
const getDefaultTab = () => {
if (!SCRIPT_PHASES.length) return undefined;
const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE;
};
🤖 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/bruno-app/src/components/RequestPane/Script/index.js` around lines
42 - 45, Update getDefaultTab to guard SCRIPT_PHASES[0] before accessing its
FIELD property, returning the existing fallback behavior when no phases are
registered. Preserve the current first-phase script selection when
SCRIPT_PHASES[0] exists.

Comment on lines +72 to 79
SCRIPT_PHASES.forEach(({ SCRIPT_TYPE }) => {
useFocusErrorLine({
uid: item.uid,
editorRef: getEditorRef(SCRIPT_TYPE),
scriptPhase: SCRIPT_TYPE,
isVisible: activeTab === SCRIPT_TYPE
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current signature/impl of useFocusErrorLine to gauge refactor scope
fd useFocusErrorLine -t f
cat -n packages/bruno-app/src/hooks/useFocusErrorLine/index.js

Repository: usebruno/bruno

Length of output: 2736


🏁 Script executed:

#!/bin/bash
set -eu

echo "== locate Script component =="
fd 'index\.js$' packages/bruno-app/src/components/RequestPane/Script -t f
rg -n "SCRIPT_PHASES|useFocusErrorLine|getEditorRef|export const" packages/bruno-app/src/components/RequestPane/Script packages/bruno-app/src -g '*.js' -g '*.ts' -g '*.tsx' -g '*.jsx' | head -200

echo "== relevant script component slice =="
fd 'index\.js$' packages/bruno-app/src/components/RequestPane/Script -t f | while read f; do
  echo "--- $f ---"
  wc -l "$f"
  sed -n '1,140p' "$f" | cat -n
done

Repository: usebruno/bruno

Length of output: 29015


🏁 Script executed:

#!/bin/bash
set -eu

echo "== useFocusErrorLine uses =="
rg -n "useFocusErrorLine\\(" packages/bruno-app/src -g '*.js' -g '*.ts' -g '*.tsx' -g '*.jsx'

echo "== package lint config / eslint plugin versions =="
fd 'eslint|package.json|\.eslintrc|rules-of-hooks' . -t f | sed 's#^\./##' | head -200 | rg -n 'eslint|package\.json|rules-of-hooks|rules' || true

Repository: usebruno/bruno

Length of output: 2151


Move useFocusErrorLine calls outside the forEach.

useFocusErrorLine uses useRef and React hooks internally, so calling it per SCRIPT_PHASES entry from a forEach callback violates the Rules of Hooks. Call it once with all editor refs/phases/config, or handle each visible phase with its own component hook call rather than in this loop.

🤖 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/bruno-app/src/components/RequestPane/Script/index.js` around lines
72 - 79, Move the useFocusErrorLine invocations out of the SCRIPT_PHASES.forEach
loop in the Script component. Preserve focus behavior for each script phase
while complying with the Rules of Hooks by calling the hook at a stable
component level or delegating each phase to a child component with its own hook
call; continue passing the corresponding item.uid, editor ref, script phase, and
visibility state.

Comment on lines +153 to +161
{hasScript && (
<StatusDot
type={
item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`]
? 'error'
: 'default'
}
/>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wrong key used for script-error lookup — error dot will never render.

item?.[${phase.SCRIPT_TYPE}ScriptErrorMessage] builds a key like pre-requestScriptErrorMessage or grpc:before-call-startScriptErrorMessage, neither of which matches any real field on item. The correct pattern — used in GrpcRequestPane for the same phases — is item[${phase.ERROR_STATE_KEY}Message] (e.g. preRequestScriptErrorMessage). As written, the StatusDot will always render type="default" and never surface script errors on this tab.

🐛 Fix the key construction
                 <StatusDot
                     type={
-                      item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`]
+                      item?.[`${phase.ERROR_STATE_KEY}Message`]
                         ? 'error'
                         : 'default'
                     }
                   />
📝 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
{hasScript && (
<StatusDot
type={
item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`]
? 'error'
: 'default'
}
/>
)}
{hasScript && (
<StatusDot
type={
item?.[`${phase.ERROR_STATE_KEY}Message`]
? 'error'
: 'default'
}
/>
)}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 153-159: A list component should have a key to prevent re-rendering
Context: <StatusDot
type={
item?.[${phase.SCRIPT_TYPE}ScriptErrorMessage]
? 'error'
: 'default'
}
/>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🤖 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/bruno-app/src/components/RequestPane/Script/index.js` around lines
153 - 161, Update the error-state lookup used by the StatusDot in the hasScript
rendering block to construct the key from phase.ERROR_STATE_KEY followed by
“Message”, matching the pattern used by GrpcRequestPane. Preserve the existing
error/default StatusDot behavior while ensuring pre-request and other script
phases resolve their actual item error fields.

preRequestTestResults={preRequestTestResults}
postResponseTestResults={postResponseTestResults}
/>
<TestResults item={item} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make runner script-error visibility phase-aware.

TestResults and TestResultsLabel now consume all phase fields, but the nearby hasScriptError and effect still only check legacy pre-request/post-response/test keys. A gRPC-only phase error can therefore omit the error icon and card. Derive both from getPhasesByRequestType(item?.type) plus the legacy test error, matching ScriptError.

Also applies to: 108-108

🤖 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/bruno-app/src/components/RunnerResults/ResponsePane/index.js` at
line 70, Update hasScriptError and the related effect in ResponsePane to derive
script errors from getPhasesByRequestType(item?.type), while also including the
legacy test error. Keep both the error icon and error-card behavior consistent
with ScriptError so gRPC-only phase errors are detected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant