Uncaught (in promise) Error: There was an error invoking the function - out is not a constructor

Subject: Critical: invoke call fails with “out is not a constructor” in minimal Forge Custom UI app (both tunnel & deploy)

Description:

We are consistently encountering a critical error when attempting to use the invoke method from @forge/bridge within a minimal Create React App (CRA) based Custom UI app. The error Error: There was an error invoking the function - out is not a constructor originates deep within the @forge/bridge and post-robot.js internals, indicating a fundamental issue with the communication channel between the Custom UI iframe and the Atlassian host product.

This issue occurs both when running forge tunnel (local development server proxied) and after forge deploy (production bundle on Atlassian’s infrastructure), indicating it is not related to production build optimizations or typical CSP differences between environments.

Steps to Reproduce (Minimal Reproduction):

  1. Initialize a new Create React App project:

    pnpx create-react-app minimal-forge-test --template typescript
    cd minimal-forge-test
    
  2. Install @forge/bridge and set React to a stable 18.x version:

    pnpm add @forge/bridge
    
  3. Address a common ESLint conflict (specific to CRA/pnpm):

    pnpm add eslint-plugin-react@latest eslint-config-react-app@latest -D
    pnpm store prune
    

    (Note: This step is needed to resolve a local build blocker, but the core issue persists after it.)

  4. Modify src/App.tsx to include the invoke call:

    import React, {useEffect} from 'react';
    import logo from './logo.svg';
    import './App.css';
    import {invoke} from '@forge/bridge'; // Ensure @forge/bridge is imported
    
    function App() {
      useEffect(() => {
        const callForgeInvoke = async () => {
          console.log("App Component: Attempting invoke..."); // This log IS seen in console
          try {
            const res = await invoke("test"); // This is the line that throws the error
            console.log("App Component: Invoke successful:", res);
          } catch (e: any) {
            console.error("App Component: Invoke failed:", e); // This catches the "out is not a constructor"
          }
        }
        callForgeInvoke();
      }, []); // Empty dependency array to run once on mount
    
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Forge App loaded. Check console for invoke attempt and error.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
        </div>
      );
    }
    
    export default App;
    
  5. Build the Custom UI project:

    pnpm run build
    

    (This should now complete successfully.)

  6. Set up the Forge Manifest (in a separate Forge project directory, e.g., ../your-forge-app-root/manifest.yml):

    # manifest.yml
    modules:
      jira:issuePanel:
        - key: llm-issue-panel
          resource: issue-panel-ui
          title: Minimal Forge App
          resolver:
            function: main
          icon: resources/icon.svg # Or provide a dummy one
    
      function:
        - key: main
          handler: index.handler # This resolver must exist and return a simple object
    
    resources:
      - key: issue-panel-ui
        path: ../minimal-forge-test/build/ # Path to the build output of your minimal CRA app
    permissions:
      content:
        scripts:
          - 'unsafe-eval' # Added this to resolve NS_ERROR_CONTENT_BLOCKED, but issue persists
        styles:
          - 'unsafe-inline'
    app:
      runtime:
        name: nodejs20.x # Or nodejs22.x - specify your Node.js runtime
    

    (Ensure resources/icon.svg exists or point to a valid resource)

  7. Create a minimal resolver function (e.g., src/index.ts in your Forge project’s functions directory):

    // src/index.ts
    import Resolver from '@forge/resolver';
    
    const resolver = new Resolver();
    
    resolver.define('test', async ({ payload, context }) => {
      console.log('Resolver received "test" call from UI.');
      return { status: 'success', message: 'Hello from Forge resolver!' };
    });
    
    export const handler = resolver.get;
    
  8. Deploy the Forge app:

    fctl deploy
    

    (This should eventually pass the linting stage, given moduleResolution: "node" is used in the main tsconfig.json for the Forge app itself if applicable, or that forge lint behaves as expected)

  9. Run with forge tunnel (or view deployed app in Jira):

    forge tunnel
    

    Navigate to the Jira issue panel where the app is embedded.

Expected Outcome:
The app should load, and the invoke("test") call should successfully communicate with the resolver.

Actual Outcome:
The app loads, the App Component: Attempting invoke... log appears, but then an Uncaught (in promise) Error: There was an error invoking the function - out is not a constructor is thrown in the browser console.

Error Stack Trace (from browser console):

Error: There was an error invoking the function - out is not a constructor
    invoke useBridge.tsx:317
    o metrics.ts:81
    f bridge-provider.js:61
    eA post-robot.js:1727
    try post-robot.js:621
    eA post-robot.js:1724
    eA post-robot.js:1807
    t post-robot.js:2135
    try post-robot.js:621
    t post-robot.js:2128
    i helpers.ts:100
    d trycatch.ts:233
    c dom.ts:107
    eL post-robot.js:2119
    X post-robot.js:906
    getOrSet post-robot.js:993
    eL post-robot.js:2117
    e post-robot.js:2199
    r post-robot.js:13
    <anonymous> useBridge.tsx:37 (relevant snippet: `createIframeBridge = require('@atlassian/bridge-core').createIframeBridge;`)
    n DP-35:74
    c async-forge-ui-issue-view-extension.ec4712b2.js:14
    React 8
    error global-bridge.js:2
    Se global-bridge.js:2
    Se global-bridge.js:2

Troubleshooting Steps Already Performed:

  • React Version: Downgraded from React 19 (beta) to stable 18.3.1.
  • @forge/bridge Version: Using latest 4.5.3.
  • CSP: Added script-src 'unsafe-eval' to manifest.yml (resolved a NS_ERROR_CONTENT_BLOCKED but not the primary error). style-src 'unsafe-inline' is also present.
  • Build Environment: Issue occurs with both forge tunnel (local dev server) and forge deploy (production build).
  • craco.config.cjs: For minimal test, no custom Craco config is used. For original app, webpack.resolve.fallback and webpack.plugins.ProvidePlugin entries (Node.js polyfills) were removed/commented out and confirmed not to be the cause.
  • TypeScript moduleResolution: For minimal CRA, it uses default Bundler. For the containing Forge app, tried node, classic, node16, nodenext to appease fctl deploy linting, but the core invoke error persists regardless.
  • Browser Isolation: Tested in Incognito mode and different browsers (Chrome, Firefox).
  • Cache Clearing: Performed rm -rf node_modules pnpm-lock.yaml, pnpm store prune, and browser cache clears (Clear site data).
  • Delay before Invoke: Introduced setTimeout (up to 1.3 seconds) before the first invoke call; the error still occurs.

Request:

This issue seems to be a fundamental problem with how @forge/bridge or its underlying communication library (post-robot) initializes or operates within the Forge Custom UI iframe, as it’s reproducible with a minimal setup and stable dependencies. We are unable to proceed with development due to this critical blocker.

Any insights or direct assistance would be greatly appreciated.

In my UI Kit projects and the example app for Jira issue panels with Custom UI, the handler is defined as handler = resolver.getDefinitions(); and not resolver.get as you have it. Might that be related? Not sure if that’s just a typo here, though, as when I try that TypeScript rightly complains that Property 'get' does not exist on type 'Resolver'.