Skip to content

Fixes #33#34

Open
cmtonkinson wants to merge 1 commit intouniversal-tool-calling-protocol:mainfrom
cmtonkinson:feat/call-tool-chain-fix
Open

Fixes #33#34
cmtonkinson wants to merge 1 commit intouniversal-tool-calling-protocol:mainfrom
cmtonkinson:feat/call-tool-chain-fix

Conversation

@cmtonkinson
Copy link

@cmtonkinson cmtonkinson commented Mar 18, 2026

This was a tough one. The naive fix, and my initial thought, was to just make the wrapper async/await the result. Turns out that broke nearly everything.

isolated-vm deadlocks when called inside an async function which itself is being awaited from another async. I tried various "simple" fixes and kept breaking synchronous tool call tests.

That mean a dual-path:

  • For IIFE, use .then() to avoid the extra nested await
  • With an explicit return, use the await wrapper ... and I have no idea how to solve this without literally parsing the code, which super sucks.

This should be considered a temporary hack until we can get to the bottom of a Real Fix(TM).


Summary by cubic

Fixes #33 by correctly returning results from async IIFEs and top‑level await in callToolChain, while keeping synchronous tool calls stable. Uses a dual-path wrapper to avoid isolated-vm deadlocks and premature Promise serialization.

  • Bug Fixes
    • Resolve async results via ivm.Reference callbacks, ensuring Promises settle before JSON serialization.
    • Detect pure-expression vs statement code to choose between a .then() expression wrapper or an async/await statement wrapper.
    • Updated docs to note that await on tools is supported; added regression tests and jest.config.cjs using ts-jest.

Written for commit 6ce36e4. Summary will update on new commits.

This was a tough one. The naive fix, and my initial thought, was to just
make the wrapper async/await the result. Turns out that broke nearly
everything.

isolated-vm deadlocks when called inside an async function which itself
is being awaited from another async. I tried various "simple" fixes and
kept breaking synchronous tool call tests.

That mean a dual-path:
- For IIFE, use .then() to avoid the extra nested await
- With an explicit return, use the await wrapper
... and I have no idea how to solve this without literally parsing the
code, which super sucks.

This should be considered a temporary hack until we can get to the
bottom of a Real Fix(TM).
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="typescript-library/src/code_mode_utcp_client.ts">

<violation number="1" location="typescript-library/src/code_mode_utcp_client.ts:262">
P2: Heuristic expression/statement detection can misclassify valid expressions, causing the statement wrapper to run and silently return undefined.</violation>

<violation number="2" location="typescript-library/src/code_mode_utcp_client.ts:287">
P2: Expression-mode wrapping breaks empty/whitespace input by generating invalid syntax (`var __userResult = ;`), regressing prior no-op behavior.</violation>

<violation number="3" location="typescript-library/src/code_mode_utcp_client.ts:319">
P1: `callToolChain` can hang indefinitely because it awaits `resultPromise` without a timeout after `script.run` timeout has already elapsed scope.</violation>
</file>

<file name="typescript-library/tests/code_mode_utcp_client.test.ts">

<violation number="1" location="typescript-library/tests/code_mode_utcp_client.test.ts:599">
P2: Regression test claims top-level await coverage but never uses `await`, so it does not validate the intended behavior.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// Run the script (starts the async IIFE); then await the callback-based promise.
await script.run(context, { timeout });
const resultJson = await resultPromise;
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: callToolChain can hang indefinitely because it awaits resultPromise without a timeout after script.run timeout has already elapsed scope.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At typescript-library/src/code_mode_utcp_client.ts, line 319:

<comment>`callToolChain` can hang indefinitely because it awaits `resultPromise` without a timeout after `script.run` timeout has already elapsed scope.</comment>

<file context>
@@ -204,25 +205,124 @@ ${interfaces.join('\n\n')}`;
+
+      // Run the script (starts the async IIFE); then await the callback-based promise.
+      await script.run(context, { timeout });
+      const resultJson = await resultPromise;
+
       // Parse the result from JSON
</file context>
Fix with Cubic

if (ch === ')' || ch === ']' || ch === '}') { depth--; i++; continue; }
if (depth === 0) {
for (const kw of stmtKeywords) {
if (s.startsWith(kw, i)) return false;
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Heuristic expression/statement detection can misclassify valid expressions, causing the statement wrapper to run and silently return undefined.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At typescript-library/src/code_mode_utcp_client.ts, line 262:

<comment>Heuristic expression/statement detection can misclassify valid expressions, causing the statement wrapper to run and silently return undefined.</comment>

<file context>
@@ -204,25 +205,124 @@ ${interfaces.join('\n\n')}`;
+          if (ch === ')' || ch === ']' || ch === '}') { depth--; i++; continue; }
+          if (depth === 0) {
+            for (const kw of stmtKeywords) {
+              if (s.startsWith(kw, i)) return false;
+            }
+          }
</file context>
Fix with Cubic

wrappedCode = `
(async function() {
try {
var __userResult = ${code};
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Expression-mode wrapping breaks empty/whitespace input by generating invalid syntax (var __userResult = ;), regressing prior no-op behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At typescript-library/src/code_mode_utcp_client.ts, line 287:

<comment>Expression-mode wrapping breaks empty/whitespace input by generating invalid syntax (`var __userResult = ;`), regressing prior no-op behavior.</comment>

<file context>
@@ -204,25 +205,124 @@ ${interfaces.join('\n\n')}`;
+        wrappedCode = `
+          (async function() {
+            try {
+              var __userResult = ${code};
+              if (__userResult && typeof __userResult.then === 'function') {
+                __userResult.then(function(v) {
</file context>
Suggested change
var __userResult = ${code};
var __userResult = ${code.trim() ? code : 'undefined'};
Fix with Cubic

expect(result.operation).toBe('addition');
});

test('should support top-level await syntax (issue #33)', async () => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P2: Regression test claims top-level await coverage but never uses await, so it does not validate the intended behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At typescript-library/tests/code_mode_utcp_client.test.ts, line 599:

<comment>Regression test claims top-level await coverage but never uses `await`, so it does not validate the intended behavior.</comment>

<file context>
@@ -580,6 +580,48 @@ describe('CodeModeUtcpClient', () => {
+    expect(result.operation).toBe('addition');
+  });
+
+  test('should support top-level await syntax (issue #33)', async () => {
+    // Top-level await works because the wrapper is now an async function.
+    const code = `
</file context>
Fix with Cubic

@h3xxit
Copy link
Member

h3xxit commented Mar 20, 2026

Yeah I was also having some issues with the whole async stuff and I'm not a TS expert. I'm not sure I understand why this is temporary fix only.

Do you think it would be easier to just remove the result altogher and standardize it so it only supports returning via console.log?

@h3xxit h3xxit self-requested a review March 20, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

call_tool_chain return value is always empty in MCP bridge; data only accessible via console.log

2 participants