-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Summary
After a successful on-prem install, the web UI loads and authentication works.
However, dragging any block (e.g., “Agent”, “Knowledge”) from the left toolbox onto the canvas does nothing. The drag events fire, but no node is created and no network request is sent. WebSocket connectivity is fine.
This happens consistently when the site is published behind Cloudflare (Free) via Cloudflare Tunnel to a private origin.
From browser instrumentation we see that, on drop
, DataTransfer.types
contains only ["application/json"]
and the payload looks like {"type":"agent"}
. If the Builder expects a custom MIME (e.g. application/reactflow
) the drop will be ignored - which seems to match the observed behavior.
Would appreciate guidance and/or a fallback in the Builder to also accept application/json
drops.
Environment
-
Host OS: Ubuntu 24.04 LTS (VM on Proxmox)
-
Docker: Engine + Portainer (stack deployment)
-
Database:
pgvector/pgvector:pg17
-
App images (latest as of 2025-09-04):
ghcr.io/simstudioai/simstudio:latest
(Next.js reports 15.4.1 in logs)ghcr.io/simstudioai/realtime:latest
(Bun runtime inside)ghcr.io/simstudioai/migrations:latest
-
Networking:
macvlan
with static LAN IPs for web and realtime- internal bridge for DB
-
Reverse proxy: Cloudflare Tunnel (Free plan) → private origins
-
Browsers tested: Chrome (latest), Firefox (latest) on Windows 11 and macOS
Deployment (Portainer stack)
Note: domains/IPs/secrets are placeholders.
version: "3.9"
networks:
macvlan_lan:
external: true
app_internal:
external: true
services:
db:
image: pgvector/pgvector:pg17
container_name: simai-db
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER} # e.g. sim
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # ****
POSTGRES_DB: ${POSTGRES_DB:-simai}
volumes:
- /data/sim/pgdata:/var/lib/postgresql/data
networks:
app_internal:
ipv4_address: 172.22.0.31
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\" -h 127.0.0.1 -p 5432 || exit 1"]
interval: 10s
timeout: 3s
retries: 30
start_period: 15s
migrations:
image: ghcr.io/simstudioai/migrations:latest
container_name: simai-migrations
restart: "no"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: ${DATABASE_URL} # postgresql://<user>:<pass>@simai-db:5432/simai
command: ["bun","x","drizzle-kit","migrate"]
networks: [app_internal]
realtime:
image: ghcr.io/simstudioai/realtime:latest
container_name: simai-realtime
restart: unless-stopped
depends_on:
db:
condition: service_healthy
migrations:
condition: service_completed_successfully
environment:
NODE_ENV: production
HOST: "0.0.0.0"
PORT: "4000"
DATABASE_URL: ${DATABASE_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BETTER_AUTH_URL: https://app.example.com
CORS_ORIGIN: https://app.example.com
expose: ["4000"]
networks:
app_internal:
macvlan_lan:
ipv4_address: 10.0.0.172
healthcheck:
test:
- CMD-SHELL
- >
node -e "const n=require('net');const s=n.connect(4000,'127.0.0.1',()=>{s.end();process.exit(0)});
s.on('error',()=>process.exit(1));setTimeout(()=>process.exit(1),3000)"
interval: 10s
timeout: 3s
retries: 30
start_period: 15s
simstudio:
image: ghcr.io/simstudioai/simstudio:latest
container_name: simai-web
restart: unless-stopped
depends_on:
realtime:
condition: service_healthy
environment:
NODE_ENV: production
TZ: Europe/Vienna
DATABASE_URL: ${DATABASE_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
BETTER_AUTH_URL: https://app.example.com
NEXT_PUBLIC_APP_URL: https://app.example.com
NEXT_PUBLIC_SOCKET_URL: wss://app.example.com/socket.io
COPILOT_API_KEY: ${COPILOT_API_KEY}
networks:
macvlan_lan:
ipv4_address: 10.0.0.171
app_internal:
ipv4_address: 172.22.0.32
healthcheck:
test:
- CMD-SHELL
- >
node -e "require('http').get('http://127.0.0.1:3000/', r=>process.exit(r.statusCode<500?0:1))
.on('error',()=>process.exit(1))"
interval: 10s
timeout: 3s
retries: 30
start_period: 25s
Cloudflare setup (Free plan)
-
Tunnel to private origins:
app.example.com
→http://10.0.0.171:3000
(Builder/UI)- (Realtime is accessed at the same host via
/socket.io
as perNEXT_PUBLIC_SOCKET_URL
.)
-
Disabled features on the hostname:
- Rocket Loader: Off
- Email Obfuscation: Off
- Automatic HTTPS Rewrites: Off
- Browser Integrity Check: Off
-
Cache Rules (bypass / no transform):
- Path prefixes:
/_next/*
,/socket.io/*
,/api/*
- Path prefixes:
-
Security Rules: only a basic allowlist rule for admin IP + a “skip” for
/workflow/
and/home/
during testing. No challenges are fired. -
Result: assets fetch 200, network quiet on drop, still no nodes created.
What works
-
Migrations: ran successfully (Drizzle). DB tables exist.
-
Usage 500s: Initially
/api/usage?context=user
returned 500 due to missing rows. We createdusage_limits
(PK=user_id) and ensureduser_stats
sequence/defaults exist. After that, no more 500s. -
WebSocket:
npx wscat -c 'wss://app.example.com/socket.io/?EIO=4&transport=websocket'
→0{"sid":"…","upgrades":[],"pingInterval":25000,"pingTimeout":60000,"maxPayload":1000000}
(also tested with-H 'Origin: https://app.example.com'
- OK)
-
Realtime port: inside container,
netstat -lntp
shows0.0.0.0:4000
(PID 1/bun) listening.
The problem (Builder drag & drop)
Steps to reproduce
- Open
https://app.example.com
(served via Cloudflare Tunnel). - Create or open a workspace → Builder canvas shows “Start” node.
- Drag any block from the left toolbox (e.g., Agent) onto the canvas.
- Release mouse over the canvas.
Expected
- A node should be created on the canvas.
- Usually this also triggers a persistence/network call for the new block.
Actual
- Nothing is created on the canvas.
- No network request is issued during/after the drop.
- No visible error. (Only React Flow deprecation warnings.)
Browser console (instrumented)
We added debug listeners:
-
dragstart
/dragover
/drop
do fire on.react-flow__pane
. -
On
drop
, the event shows:e.dataTransfer.types
→["application/json"]
e.dataTransfer.getData("application/json")
→{"type":"agent"}
(or similar)
-
No call to
/api/...
after the drop; Network tab stays idle.
The only messages we see are React Flow deprecations, e.g.:
[DEPRECATED] `project` is deprecated. Instead use `screenToFlowPosition`.
There is no need to subtract the react flow bounds anymore!
Hypothesis
Behind Cloudflare, the custom MIME type expected by the Builder for drag data (e.g., something like application/reactflow
) appears to be missing/normalized to application/json
. The UI then ignores the drop because the type check fails, which matches the symptom “events fire, but nothing is created, and no network call is made.”
We already disabled common CF features (Rocket Loader, Obfuscation, HTTPS rewrites) and created cache bypass rules for /_next/*
, /socket.io/*
, /api/*
, but the behavior persists on the Free plan.
A robust fix on the app side could be:
- accept both the custom MIME and a JSON fallback (
application/json
) if the payload matches the expected schema, or - avoid strict MIME checks and rely on a schema check, or
- document the exact MIME(s) the Builder expects so we can tune the proxy to preserve them.
What we already tried
-
Recreated Cloudflare public hostnames and Tunnel routes.
-
Turned off Rocket Loader, Email Obfuscation, Auto HTTPS Rewrites, BIC.
-
Added cache rules to bypass transforms for
/_next/*
,/socket.io/*
,/api/*
. -
Cleared site data (Application → Storage → Clear site data), hard reload, incognito.
-
Verified CORS:
NEXT_PUBLIC_APP_URL=https://app.example.com
NEXT_PUBLIC_SOCKET_URL=wss://app.example.com/socket.io
- Realtime
CORS_ORIGIN=https://app.example.com
-
Verified WebSocket works end-to-end (Engine.IO handshake OK).
-
Database fully migrated; usage tables fixed;
/api/usage?context=user
200 now.
Suggested quick repo steps for maintainers
- Deploy the stack above (any LAN IPs).
- Put the site behind a Cloudflare Tunnel (Free).
- Disable Rocket Loader/Obfuscation/Rewrites; add the cache bypass rules listed.
- Open the Builder and try to drag “Agent” onto the canvas.
- In DevTools Console, run a quick probe:
document.addEventListener('drop', e => {
console.log('types:', [...e.dataTransfer.types]);
try {
console.log('json:', e.dataTransfer.getData('application/json'));
} catch {}
}, {capture:true});
You should see types: ["application/json"]
and a valid JSON payload, but no node appears and no network call happens.
Questions
- What MIME type(s) does the Builder expect on
DataTransfer
? - Can the Builder accept
application/json
as a safe fallback? - Are there any known Cloudflare interactions (especially on the Free plan) that break the drag data MIME and should be documented?
- If a specific MIME is required, could it be feature-flagged or configured so self-hosters can adapt to proxies that rewrite types?
Misc. logs (snippets)
Realtime health
tcp 0 0 0.0.0.0:4000 0.0.0.0:* LISTEN 1/bun
WebSocket handshake
$ npx wscat -c 'wss://app.example.com/socket.io/?EIO=4&transport=websocket'
Connected
< 0{"sid":"…","upgrades":[],"pingInterval":25000,"pingTimeout":60000,"maxPayload":1000000}
Next.js startup (simstudio)
▲ Next.js 15.4.1
- Local: http://localhost:3000
- Network: http://0.0.0.0:3000
Thanks a lot!