Skip to content

[BUG] Blocks cannot be dropped onto the canvas in the Builder (drag & drop ignored) when app is served via Cloudflare; WebSocket OK, no network errors #1268

@Nimkaru

Description

@Nimkaru

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.comhttp://10.0.0.171:3000 (Builder/UI)
    • (Realtime is accessed at the same host via /socket.io as per NEXT_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/*
  • 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 created usage_limits (PK=user_id) and ensured user_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 shows 0.0.0.0:4000 (PID 1/bun) listening.


The problem (Builder drag & drop)

Steps to reproduce

  1. Open https://app.example.com (served via Cloudflare Tunnel).
  2. Create or open a workspace → Builder canvas shows “Start” node.
  3. Drag any block from the left toolbox (e.g., Agent) onto the canvas.
  4. 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

  1. Deploy the stack above (any LAN IPs).
  2. Put the site behind a Cloudflare Tunnel (Free).
  3. Disable Rocket Loader/Obfuscation/Rewrites; add the cache bypass rules listed.
  4. Open the Builder and try to drag “Agent” onto the canvas.
  5. 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions