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):
-
Initialize a new Create React App project:
pnpx create-react-app minimal-forge-test --template typescript cd minimal-forge-test -
Install
@forge/bridgeand set React to a stable 18.x version:pnpm add @forge/bridge -
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.)
-
Modify
src/App.tsxto include theinvokecall: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; -
Build the Custom UI project:
pnpm run build(This should now complete successfully.)
-
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.svgexists or point to a valid resource) -
Create a minimal resolver function (e.g.,
src/index.tsin your Forge project’sfunctionsdirectory):// 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; -
Deploy the Forge app:
fctl deploy(This should eventually pass the linting stage, given
moduleResolution: "node"is used in the maintsconfig.jsonfor the Forge app itself if applicable, or that forge lint behaves as expected) -
Run with
forge tunnel(or view deployed app in Jira):forge tunnelNavigate 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/bridgeVersion: Using latest4.5.3.- CSP: Added
script-src 'unsafe-eval'tomanifest.yml(resolved aNS_ERROR_CONTENT_BLOCKEDbut not the primary error).style-src 'unsafe-inline'is also present. - Build Environment: Issue occurs with both
forge tunnel(local dev server) andforge deploy(production build). craco.config.cjs: For minimal test, no custom Craco config is used. For original app,webpack.resolve.fallbackandwebpack.plugins.ProvidePluginentries (Node.js polyfills) were removed/commented out and confirmed not to be the cause.- TypeScript
moduleResolution: For minimal CRA, it uses defaultBundler. For the containing Forge app, triednode,classic,node16,nodenextto appeasefctl deploylinting, but the coreinvokeerror 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 firstinvokecall; 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.