Skip to content

mastra dev drops workspace-package exports that only tool files import #27086

Open

Description

@mike-4040

DevBundler.watch() passes analysisEntries: [entryFile, <#server stub>] to getWatcherInputOptions(), then bundles every file getAllToolPaths() finds under src/mastra/tools/**. The workspace-package bundles in node_modules/.cache/ export only the names the analysis saw. A tool file that imports a name nothing else imports fails at boot:

SyntaxError: The requested module '…/node_modules/.cache/<pkg>.mjs' does not provide an export named 'X'

Bundler._bundle() (used by mastra build) includes the resolved tool paths in analyzeBundle(), so build works and dev doesn't. --tools can't work around it, because getAllToolPaths() always adds the default glob. Present in mastra 1.31.3 through 1.34.1.

Fix: call listToolsInputOptions(toolsPaths) before getWatcherInputOptions() and add ...Object.values(toolsInputOptions) to analysisEntries.

Repro: a monorepo workspace package exporting A and B; src/mastra/index.ts imports A; a file under src/mastra/tools/ imports B and is unreachable from the entry. Run mastra dev.

Activity

  1. dane-ai-mastra commented on Oct 14, 2026

    @dane-ai-mastra
    Contributor

    Thank you for opening this issue. It is waiting for maintainer triage or approval.

    Please wait until maintainers remove the triage or approval label before opening a pull request.

  2. mastra-platform commented on Oct 15, 2026

    @mastra-platform
    Contributor
    Type bug — mastra dev analyzes only the app entry + a #server stub, so the per-workspace-package .cache module never exposes exports that only tool files import, and dev fails to boot with a missing-export SyntaxError
    Route Plan fix
    Severity 🟠 high — mastra dev crashes at startup for a supported monorepo shape; a one-line import workaround exists
    Confidence high — reproduced the exact reported error on published mastra@1.34.1, located the dev/build asymmetry at line level, and verified the proposed fix resolves it
    Effort low — one localized change in DevBundler.watch() that mirrors what the build path already does, plus a regression fixture
    Impact medium — dev-only; requires a tool file that is not statically reachable from the app entry and imports a workspace-package export no entry-reachable module imports
    Next step Plan the fix in DevBundler.watch() (add tool paths to analysisEntries) with a monorepo dev regression fixture containing an unregistered tool; decide on the contributor's assignment request

    Understanding

    Genuine bug, confirmed by reproduction. The trigger condition is narrower than the report implies and matters for the fix and its test: the tool file must not be statically reachable from the app entry (a tool discovered only by the src/mastra/tools/** glob, e.g. not registered on any agent/MCP server). A tool that an agent imports is part of the entry graph, so its imports are analyzed and dev boots fine.

    Causal chain

    1. Dev analysis entries exclude tool files. packages/cli/src/commands/dev/DevBundler.ts:124 passes analysisEntries: [entryFile, devServerAnalysisEntry]; the stub (:111-115) imports only @mastra/core/evals/scoreTraces and #server. listToolsInputOptions(toolsPaths) is called afterwards (:127) and is used only as Rollup input and by tools-watcher.
    2. The analyzer records only observed import bindings. packages/deployer/src/build/analyze/analyzeEntry.ts:168 iterates output.importedBindings and :187-193 stores them as exports: bindings on depsToOptimize. A name imported only by an unanalyzed file never enters that set (workspace deps never get the '*' wildcard, which is reserved for unresolved dynamic imports).
    3. That set is the cache module's export surface. workspace-deps-watcher.ts pre-transpiles each used workspace package to <pkg>/node_modules/.cache/<dep>.mjs, and packages/deployer/src/build/analyze/bundleExternals.ts:48-103 synthesizes the virtual module (export { …observed names… } from '<dep>'). Observed in the repro: the cache file contained exactly export { alpha };.
    4. Tool output consumes that module at runtime. Dev's single watch graph still compiles tool files (input keys tools/<key>), tools-watcher writes tools.mjs importing every one, and packages/cli/src/public/templates/dev.entry.js imports #tools → the tool chunk's import { beta } from '<cache>' fails at ESM instantiation before the server starts.

    Why build is unaffected: packages/deployer/src/bundler/index.ts:638-640 calls analyzeBundle([serverFile, ...additionalEntries, ...Object.values(resolvedToolsPaths)], …), i.e. build does include tool paths as analysis entries. Dev is the only gap. The issue's proposed fix matches the build behavior.

    History: introduced by #18765 (4ffd33742, 2026-07-02, "fix(deployer): bundle local workspace deps in dev"), which added dev-side analysisEntries + the #server stub with the intent that watcher analysis include extra entry points so local workspace deps are discovered; tool entry points were never added to that set. No changelog entry records it.

    Verified fix: hoist listToolsInputOptions(toolsPaths) above the getWatcherInputOptions() call and pass analysisEntries: [entryFile, devServerAnalysisEntry, ...Object.values(toolsInputOptions)]. Patched into the published mastra@1.34.1 bundle and re-run: dev boots, the cache module becomes export { alpha, beta };, /api/agents returns 200. Note the build path passes entryProjectRoot to listToolsInputOptions while dev does not (defaults to cwd) — harmless for analysis entries, but worth aligning.

    Workaround for users: import the symbol from an entry-reachable module (the app's src/mastra/index.ts, or the agent/MCP server that registers the tool). Verified on unpatched mastra@1.34.1: dev boots and the cache exports both names. mastra build + mastra start are unaffected.

    Test coverage: none for this. e2e-tests/monorepo dev suite (monorepo.test.ts:316, sharing runApiTests at :433) passes despite the template containing the same shape (apps/custom/src/mastra/tools/calculator-tool.ts imports roundToOneNumber from the @inner/hello-world barrel, which the entry never imports) — because agents/index.ts:9-11 and mcp/index.ts import that tool, pulling it into the entry graph. A regression fixture must therefore add a tool file that nothing imports. No unit test asserts dev depsToOptimize/workspaceMap contents or cache-module export surfaces.

    Risk to plan around: more analysis entries means more analyzer work per dev start; #12843 (dev hang in large monorepos via recursive analyzeBundle) came from this same function. Tool files are already in the watch graph, so the added cost should be mostly shared — worth a startup-time sanity check on a large monorepo.

    Related: #27033 (build fails for versionless workspace packages), #26540 (dev fails when an apiRoute imports a raw-TS workspace package), #19380 (bundler.transpilePackages ignored), #22892 (per-workspace .cache is consumer-shaped), #26321, #20248, #12843. Re-checked 2026-10-15 after a new comment: still no PR references #27086, and origin/main (04b47753) still has analysisEntries: [entryFile, devServerAnalysisEntry] at DevBundler.ts:124, so the bug is unfixed. The new comment (@akhil9tiet asking to be assigned) adds no technical information; the verdict is unchanged.

    Assumptions

    • Read the report literally: workspace package + tool file under src/mastra/tools/ importing a name no entry-reachable module imports.
    • Dev-only; mastra build and start are unaffected (build already adds tool paths as analysis entries).
    • Not a package-manager/resolution defect — the .cache file is generated correctly for the exports the analysis saw.
    • Trigger requires an unregistered/entry-unreachable tool file; a registered tool masks the bug (explains green e2e).
    • Severity and impact judged independently: boot crash (high) vs. narrow trigger with a one-line workaround (medium).
    • Domain label CLI: the change lands in packages/cli/src/commands/dev/DevBundler.ts; packages/core is untouched, so no @mastra/core.
    • The verified fix is the issue's proposed shape; final implementation still needs review.
    • The assignment request is a process matter, not new evidence; it does not change classification or route.

    Open questions

    • Contributor @akhil9tiet asked to be assigned (2026-10-15). A maintainer must decide whether to assign them or take the fix internally.
    • Should the same treatment cover the other convention-discovered class dev adds via fs-routing-watcher / collectInlinedPaths (fs agents whose files nothing entry-reachable imports)? I did not investigate whether inlined fs-agent paths can hit the same missing-export gap — worth deciding scope before planning.

    Reproduction

    Reproduced 2026-10-15 against published packages (mastra@1.34.1, @mastra/core@1.76.0, Node 24.17.0, pnpm 12.8.1) in a scratch monorepo at /tmp/repro-27086 (throwaway, not in the repo):

    1. pnpm workspace with packages/* and apps/*.
    2. packages/ws-lib — @ws/lib, "type": "module", main: src/index.ts, exporting alpha() and beta().
    3. apps/app deps: mastra@1.34.1, @mastra/core@1.76.0, zod, @ws/lib: workspace:*.
    4. apps/app/src/mastra/index.ts — import { alpha } from '@ws/lib'; export const mastra = new Mastra({});
    5. apps/app/src/mastra/tools/uses-beta.ts — import { beta } from '@ws/lib'; export const betaValue = beta(); (nothing imports this file).
    6. pnpm install --config.minimumReleaseAge=0 --allow-build=esbuild; run with MASTRA_PORT=4555 ./node_modules/.bin/mastra dev (invoke ./node_modules/.bin/mastra directly — pnpm exec re-verifies the lockfile and fails ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION on just-published @mastra/* packages).

    Result — the reported failure verbatim:

    import { beta } from '/private/tmp/repro-27086/packages/ws-lib/node_modules/.cache/@ws__lib.mjs';
             ^^^^
    SyntaxError: The requested module '…/node_modules/.cache/@ws__lib.mjs' does not provide an export named 'beta'
    

    packages/ws-lib/node_modules/.cache/@ws__lib.mjs contained only:

    const alpha = () => "alpha";
    const beta = () => "beta";
    export { alpha };

    Verification of the direction: with watch() locally patched to include tool paths in analysisEntries, dev boots ("mastra 1.34.1 ready in 1107 ms", API on 4112, /api/agents → 200) and the cache module becomes export { alpha, beta };. Adding import { beta } from '@ws/lib' to src/mastra/index.ts on unpatched 1.34.1 produces the same result (workaround confirmed).

  3. akhil9tiet commented on Oct 15, 2026

    @akhil9tiet

    I'd like to work on this — please assign it to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues regarding Mastra CLI commands: Build, Dev, Start etceffort:lowimpact:mediumstatus: auto-triagedIssue entered the automatic triage queue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions