#Running dynamic code in Convex actions: new AsyncFunction() blocked?

6 messages · Page 1 of 1 (latest)

strange prairie
#

Hey everyone! I'm building a tool execution system where custom tool handlers are stored as code strings in the database and need to be executed at runtime inside a Convex internalAction. When I try to use new AsyncFunction("args", "context", "fetch", handlerCode) I get EvalError: Code generation from strings disallowed for this context. I originally had this running on a Cloudflare Worker but hit the exact same restriction there. Is there any way to enable dynamic code evaluation in Convex actions, or is this a hard V8 sandbox limitation?

If not, what's the recommended pattern for this use case? The tool handlers are defined by users in their workspace, synced to Convex as serialized code strings, and executed when an AI agent calls them. I'm considering either pre-registering handlers as named functions with a dispatch table, or using something like quickjs-emscripten as an embedded interpreter. Would love to hear if anyone has solved a similar problem or if there's a Convex-native approach I'm missing. Thanks!

white nacelleBOT
#

Hi @strange prairie. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

This is a hard limitation of the Convex runtime: dynamic code generation via new Function, eval, or new AsyncFunction is not allowed in the sandboxed V8 environment Convex uses for functions. The knowledge sources don’t explicitly call out new Function, but they do describe a tightly controlled, deterministic V8-based runtime with sandboxing and bundling, which is exactly the kind of environment that disables string-based code execution for safety and determinism.[How Convex works; Runtimes]

There’s no documented way to “turn this on” in either:

  • The default Convex runtime (V8-based, Cloudflare-Workers-like)[Runtimes]
  • Node actions with "use node" (these are still bundled and subject to platform limits; dynamic imports and similar patterns are explicitly called out as problematic)[Bundling limits; Node.js runtime]

So you should assume: you cannot safely or officially enable dynamic evaluation of arbitrary code strings in Convex functions or actions.

Recommended patterns for your use case

Given that, you’ll want to redesign the “user-defined tools” layer so it doesn’t rely on arbitrary JS evaluation inside Convex:

  1. Dispatch table of pre-registered functions

    Instead of storing executable JS, store metadata that selects from a fixed set of tool implementations you ship in your convex/ code. For example:

#
  • In DB: { toolKey: "sendSlackMessage", config: {...} }

    • In code: a map from toolKey → actual function implementation.
    • Your action / internalAction just looks up the tool by key and calls the corresponding function.

    This keeps all executable code in the bundled Convex functions (which is what the platform is designed for) and lets users configure behavior via data, not code.

  1. Use Convex actions + tools with Convex context

    If these tools are being called by an AI agent, you can expose them as tools that have access to Convex’s ActionCtx (including runQuery, runMutation, etc.) and keep the logic in normal TypeScript functions, not dynamically generated code.[Tools with context]

    Example pattern:

    // convex/tools.ts
    import { createTool } from "convex/ai"; // conceptual, see docs
    import { api } from "./_generated/api";
    
    export const sendSlackMessage = createTool({
      description: "Send a Slack message",
      args: z.object({
        channelId: z.string(),
        text: z.string(),
      }),
      handler: async (ctx, args) => {
        // ctx has ActionCtx fields like runMutation, auth, etc.
        // plus agent/thread metadata
        await ctx.runMutation(api.slackMessages.logMessage, args);
        // call Slack via fetch or Node action, depending on SDK needs
      },
    });
    

    Then your agent chooses which tool to call based on the tool name; you don’t need to eval user code.

  2. If you truly need Node-only SDKs

#
  • Put those in "use node" action files and call them via ctx.runMutation / ctx.runQuery to interact with Convex DB.[Node.js runtime; Action context]
    • Avoid runAction unless you’re actually crossing runtimes; otherwise just call plain TS helpers from the same runtime.[Best practices runAction]
  1. Embedded interpreter (e.g. quickjs-emscripten)

    The docs don’t mention or endorse running an embedded interpreter like QuickJS inside Convex. Given bundling limits and the general stance on dynamic dependencies, this is likely to be:

    • Large in bundle size (risking the 32 MiB limit).[Bundling limits]
    • Tricky to get working due to WASM / dynamic file requirements (similar to the tiktoken / sharp / pdf-parse caveats).[Bundling limits]

    The knowledge sources don’t say it’s impossible, but they also don’t document it as a supported pattern. I can’t confidently recommend it as a “Convex-native” solution based on the docs.

Summary

#
  • The EvalError: Code generation from strings disallowed for this context is effectively a hard sandbox limitation in Convex’s V8-based environment; there’s no supported way to enable new AsyncFunction / eval.
  • The Convex-native approach is to:
    • Keep all executable logic as statically bundled TypeScript/JavaScript in convex/.
    • Represent user customization as data (configs, parameters, tool selection) rather than arbitrary code.
    • Use actions (default or "use node") and tools-with-context to bridge to external services and the database.

If you share a concrete example of the kind of “user code” you’re letting people write, I can help sketch how to model it as data + a dispatch layer within Convex’s constraints.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: