Skip to content

feat: MCP server 2.0 #2384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Aug 20, 2025
Merged

feat: MCP server 2.0 #2384

merged 46 commits into from
Aug 20, 2025

Conversation

ericallam
Copy link
Member

@ericallam ericallam commented Aug 13, 2025

mcp-demo-high-res.mp4

Copy link

changeset-bot bot commented Aug 13, 2025

🦋 Changeset detected

Latest commit: cb1ff21

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
trigger.dev Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/python Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/zod-worker Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a comprehensive MCP (Model Context Protocol) integration and CLI tooling: new MCP server install script and CLI commands (mcp, install-mcp, install-rules), extensive MCP client modules (auth, context, logger, capabilities, tools, schemas, utils, formatters), rules manifest and installer, and various CLI flows and config additions. Extends packages/core v3 with deployments, trace retrieval, and date parsing. Enhances the webapp with many new Remix API routes (orgs, projects, deployments, runs trace, workers-by-tag, JWT issuance, dev-status, branches) and route-builder resource binding. Adds detailed trace/event-store support, filesystem/config utilities, package.json script/deps updates, and .gitignore entry.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mcp-server

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 30

🔭 Outside diff range comments (3)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (3)

129-134: Non-dev environments aren’t scoped to user membership (userId param ignored)

In the non-dev branch, userId is unused. If this function is reused elsewhere (as intended), it could return environments for users without org membership. Scope the lookup via the project’s organization membership to match the loader’s access control.

Apply this diff:

-  const environment = await prisma.runtimeEnvironment.findFirst({
-    where: {
-      projectId,
-      slug,
-    },
-  });
+  const environment = await prisma.runtimeEnvironment.findFirst({
+    where: {
+      projectId,
+      slug,
+      project: {
+        organization: {
+          members: {
+            some: { userId },
+          },
+        },
+      },
+    },
+  });

Note: If your Prisma schema uses different relation names, adapt the nested path accordingly.


136-141: Incorrect error message for preview environments

“Preview” currently falls through to “Production environment not found”. Include a distinct Preview label.

Apply this diff:

-    return {
-      success: false,
-      error: `${env === "staging" ? "Staging" : "Production"} environment not found`,
-    };
+    return {
+      success: false,
+      error: `${
+        env === "staging" ? "Staging" : env === "preview" ? "Preview" : "Production"
+      } environment not found`,
+    };

73-91: Service function exported from a route — move to services for reuse/testability

Exporting getEnvironmentFromEnv from a route couples routing and service logic. Move it under app/services (e.g., app/services/environments.server.ts) and import from routes. This improves reuse, testing, and avoids cross-route imports.

🧹 Nitpick comments (27)
packages/cli-v3/src/cli/common.ts (1)

71-71: Avoid blank outro on empty message

Using nullish coalescing won’t cover empty strings, resulting in a blank outro when new OutroCommandError() is thrown without a message. Prefer trimming and defaulting.

Apply this diff:

-      outro(e.message ?? "Operation cancelled");
+      outro(e.message?.trim() ? e.message : "Operation cancelled");
apps/webapp/app/services/authorization.server.ts (1)

3-3: Add deployments to ResourceTypes — ensure coverage

Including "deployments" in ResourceTypes cleanly extends existing authorization matching for read/write scopes. Please add unit tests that assert:

  • read:deployments and read:deployments: cases authorize correctly
  • Non-matching scopes are denied with the expected error message
packages/cli-v3/src/mcp/schemas.ts (1)

3-8: Tighten validation: enforce proj_ prefix and trim whitespace

Schema currently describes the “proj_” prefix but doesn’t enforce it. Add trim and a regex to catch typos early.

Apply this diff:

 export const ProjectRefSchema = z
-  .string()
+  .string()
+  .trim()
+  .regex(/^proj_.+$/, "Project ref must start with 'proj_'")
   .describe(
     "The trigger.dev project ref, starts with proj_. We will attempt to automatically detect the project ref if running inside a directory that includes a trigger.config.ts file, or if you pass the --project-ref option to the MCP server."
   )
   .optional();
packages/cli-v3/src/mcp/utils.ts (1)

15-35: Consider adding 'stack' property to error sanitization.

The current implementation only extracts name and message properties from errors. Including the stack property would provide valuable debugging information while maintaining security.

Apply this diff to include stack traces in error responses:

-  const errorProps = ["name", "message"] as const;
+  const errorProps = ["name", "message", "stack"] as const;
packages/cli-v3/src/mcp/logger.ts (2)

1-3: Verify Node.js compatibility for file system operations.

The use of appendFileSync may cause blocking behavior in the event loop. Consider using the asynchronous version for better performance.

Apply this diff to use async file operations:

-import { appendFileSync } from "node:fs";
+import { appendFile } from "node:fs/promises";

Then update the log method to be async:

-  log(message: string, ...args: unknown[]) {
+  async log(message: string, ...args: unknown[]) {
     const logMessage = `[${new Date().toISOString()}][${this.formatServerInfo()}] ${message} - ${util.inspect(
       args,
       {
         depth: null,
         colors: false,
       }
     )}\n`;
-    appendFileSync(this.filePath, logMessage);
+    await appendFile(this.filePath, logMessage);
   }

29-32: Improve null safety for client information.

The current implementation correctly handles undefined client names, but the double property access (this.server.server.getClientVersion()?.name) could be simplified.

Apply this diff to simplify the property access:

   private formatClientName() {
-    const clientName = this.server.server.getClientVersion()?.name;
+    const clientVersion = this.server.server.getClientVersion();
+    const clientName = clientVersion?.name;
     return `client=${clientName ?? "unknown"}`;
   }
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (1)

36-43: Consider simplifying search parameter parsing logic.

The current implementation has nested parsing logic that could be streamlined for better readability.

Apply this diff to simplify the parameter extraction:

-  const url = new URL(request.url);
-  const searchObject = Object.fromEntries(url.searchParams.entries());
-
-  const searchParams = SearchParamsSchema.safeParse(searchObject);
-
-  const source = (searchParams.success ? searchParams.data.source : undefined) ?? "cli";
-  const clientName = (searchParams.success ? searchParams.data.clientName : undefined) ?? "unknown";
+  const url = new URL(request.url);
+  const searchParams = SearchParamsSchema.parse({
+    source: url.searchParams.get("source"),
+    clientName: url.searchParams.get("clientName"),
+  });
+  
+  const source = searchParams.source ?? "cli";
+  const clientName = searchParams.clientName ?? "unknown";
packages/cli-v3/src/utilities/fileSystem.ts (2)

54-68: Consider validating home directory path before using it

The expandTilde function should handle the edge case where homedir() returns an empty string or undefined, which can happen in certain environments.

 export function expandTilde(filePath: string) {
   if (typeof filePath !== "string") {
     throw new TypeError("Path must be a string");
   }
 
+  const home = homedir();
+  if (!home) {
+    throw new Error("Unable to determine home directory");
+  }
+
   if (filePath === "~") {
-    return homedir();
+    return home;
   }
 
   if (filePath.startsWith("~/")) {
-    return pathModule.resolve(homedir(), filePath.slice(2));
+    return pathModule.resolve(home, filePath.slice(2));
   }
 
   return pathModule.resolve(filePath);
 }

125-133: Consider extracting line ending normalization into a utility function

The pattern fileContents.replace(/\r\n/g, "\n") is repeated in both safeReadTomlFile and safeReadJSONCFile. Consider extracting this to reduce duplication.

+function normalizeLineEndings(content: string): string {
+  return content.replace(/\r\n/g, "\n");
+}
+
 export async function safeReadTomlFile(path: string) {
   const fileExists = await pathExists(path);
 
   if (!fileExists) return;
 
   const fileContents = await readFile(path);
 
-  return parseTOML(fileContents.replace(/\r\n/g, "\n"));
+  return parseTOML(normalizeLineEndings(fileContents));
 }

And apply the same change to safeReadJSONCFile:

 export async function safeReadJSONCFile(path: string) {
   const fileExists = await pathExists(path);
 
   if (!fileExists) return;
 
   const fileContents = await readFile(path);
 
-  return parseJSONC(fileContents.replace(/\r\n/g, "\n"));
+  return parseJSONC(normalizeLineEndings(fileContents));
 }
packages/cli-v3/install-mcp.sh (3)

70-75: Add fallback mechanism for Node.js discovery

The script fails if Node.js is not in PATH, but it could be available at common locations. Consider checking standard installation paths as a fallback.

 # Get the absolute path to the node binary
 NODE_PATH=$(which node)
 if [ -z "$NODE_PATH" ]; then
-    echo "❌ Error: Node.js not found in PATH"
-    echo "Please ensure Node.js is installed and available in your PATH"
-    exit 1
+    # Try common Node.js locations as fallback
+    for node_path in /usr/local/bin/node /usr/bin/node ~/.nvm/versions/node/*/bin/node; do
+        if [ -x "$node_path" ]; then
+            NODE_PATH="$node_path"
+            echo "⚠️  Node.js not found in PATH, using: $NODE_PATH"
+            break
+        fi
+    done
+    
+    if [ -z "$NODE_PATH" ]; then
+        echo "❌ Error: Node.js not found in PATH or common locations"
+        echo "Please ensure Node.js is installed and available in your PATH"
+        exit 1
+    fi
 fi

319-392: VS Code configuration uses different key structure

The VS Code configuration uses servers instead of mcpServers, which is correct, but the inconsistency should be clearly documented to avoid confusion.

Add a comment to clarify the structural difference:

 # Function to install for VS Code
 install_vscode() {
     echo ""
     echo "🔧 Installing for VS Code..."
+    # Note: VS Code uses 'servers' key instead of 'mcpServers' used by other tools
     
     local VSCODE_DIR="$HOME/Library/Application Support/Code/User"

549-576: Add error handling for failed installations in 'all' mode

When installing for all targets, if one installation fails, the script continues with the rest. Consider tracking failures and reporting them at the end.

     all)
+        failed_targets=""
+        
+        install_claude || failed_targets="$failed_targets claude"
+        install_claude_desktop || failed_targets="$failed_targets claude-desktop"
+        install_cursor || failed_targets="$failed_targets cursor"
+        install_vscode || failed_targets="$failed_targets vscode"
+        install_crush || failed_targets="$failed_targets crush"
+        install_windsurf || failed_targets="$failed_targets windsurf"
+        
+        if [ -n "$failed_targets" ]; then
+            echo ""
+            echo "⚠️  Some installations failed: $failed_targets"
+            echo "You can retry individual targets with: $0 -t <target>"
+        fi
-        install_claude
-        install_claude_desktop
-        install_cursor
-        install_vscode
-        install_crush
-        install_windsurf
         ;;
apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1)

18-25: Missing headers parameter in findResource function

The findResource function signature in the route configuration doesn't match the expected signature from createLoaderApiRoute. The function should accept three parameters (params, authentication, searchParams) but only two are being used.

-    findResource: (params, auth) => {
+    findResource: (params, auth, searchParams) => {
       return $replica.taskRun.findFirst({
         where: {
           friendlyId: params.runId,
           runtimeEnvironmentId: auth.environment.id,
         },
       });
     },
apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1)

16-20: Consider adding shouldRetryNotFound for consistency

Other routes like api.v1.runs.$runId.trace.ts include shouldRetryNotFound: true in their configuration. Consider whether this route should also support retry logic for consistency.

     corsStrategy: "none",
+    shouldRetryNotFound: true,
     authorization: {
       action: "write",
packages/cli-v3/src/commands/login.ts (1)

349-378: Exporting getPersonalAccessToken is the right move for MCP reuse; add an explicit return type and tighten telemetry.

Now that this is a public API, lock in its contract and avoid surprises in downstream users. Also consider recording non-AbortError exceptions for observability (while keeping AbortErrors quiet to avoid retry noise).

Suggested changes:

  • Add an explicit return type.
  • Record non-AbortError exceptions as well.
-export async function getPersonalAccessToken(apiClient: CliApiClient, authorizationCode: string) {
+export async function getPersonalAccessToken(
+  apiClient: CliApiClient,
+  authorizationCode: string
+): Promise<{ token: string; obfuscatedToken: string }> {
   return await tracer.startActiveSpan("getPersonalAccessToken", async (span) => {
     try {
       const token = await apiClient.getPersonalAccessToken(authorizationCode);
@@
-    } catch (e) {
-      if (e instanceof AbortError) {
-        recordSpanException(span, e);
-      }
+    } catch (e) {
+      // Only record non-AbortError to avoid flooding telemetry during retries,
+      // but still capture unexpected errors for visibility.
+      if (!(e instanceof AbortError)) {
+        recordSpanException(span, e);
+      }
 
       span.end();
 
       throw e;
     }
   });
 }
apps/webapp/app/routes/api.v1.orgs.ts (1)

14-23: Select only required columns to reduce payload and improve performance.

Use Prisma select to avoid fetching unused columns.

-  const orgs = await prisma.organization.findMany({
-    where: {
-      deletedAt: null,
-      members: {
-        some: {
-          userId: authenticationResult.userId,
-        },
-      },
-    },
-  });
+  const orgs = await prisma.organization.findMany({
+    where: {
+      deletedAt: null,
+      members: {
+        some: {
+          userId: authenticationResult.userId,
+        },
+      },
+    },
+    select: {
+      id: true,
+      title: true,
+      slug: true,
+      createdAt: true,
+    },
+  });
packages/cli-v3/src/mcp/capabilities.ts (1)

3-31: DRY up capability checks with a single helper.

All three functions share identical logic. Simplify and centralize to reduce maintenance and ensure consistent behavior.

-import { McpContext } from "./context.js";
-
-export function hasRootsCapability(context: McpContext) {
-  const capabilities = context.server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "roots" in capabilities && typeof capabilities.roots === "object";
-}
-
-export function hasSamplingCapability(context: McpContext) {
-  const capabilities = context.server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "sampling" in capabilities && typeof capabilities.sampling === "object";
-}
-
-export function hasElicitationCapability(context: McpContext) {
-  const capabilities = context.server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "elicitation" in capabilities && typeof capabilities.elicitation === "object";
-}
+import { McpContext } from "./context.js";
+
+function hasCapability(context: McpContext, key: "roots" | "sampling" | "elicitation"): boolean {
+  const capabilities = context.server.server.getClientCapabilities();
+  return !!capabilities && typeof (capabilities as any)[key] === "object";
+}
+
+export function hasRootsCapability(context: McpContext) {
+  return hasCapability(context, "roots");
+}
+
+export function hasSamplingCapability(context: McpContext) {
+  return hasCapability(context, "sampling");
+}
+
+export function hasElicitationCapability(context: McpContext) {
+  return hasCapability(context, "elicitation");
+}
packages/cli-v3/src/mcp/mintlifyClient.ts (4)

18-24: Prefer early status validation and clearer content-type branching.

Consider guarding on response.ok before parsing (see previous diff), and make the content-type check more robust to casing/parameters.

-async function parseResponse(response: Response) {
-  if (response.headers.get("content-type")?.includes("text/event-stream")) {
+async function parseResponse(response: Response) {
+  const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
+  if (contentType.includes("text/event-stream")) {
     return parseSSEResponse(response);
   } else {
     return parseJSONResponse(response);
   }
 }

26-29: Handle invalid JSON and return a predictable error.

Wrap response.json() to avoid unhandled exceptions on malformed payloads.

-async function parseJSONResponse(response: Response) {
-  const data = await response.json();
-  return data;
-}
+async function parseJSONResponse(response: Response) {
+  try {
+    return await response.json();
+  } catch (e) {
+    throw new Error(`Invalid JSON response: ${e instanceof Error ? e.message : String(e)}`);
+  }
+}

63-73: Use a unique JSON-RPC id to avoid collisions across requests.

A static id: 1 can cause confusion in clients or intermediaries. Use a simple monotonically increasing or timestamp-based id.

-function callToolBody(tool: string, args: Record<string, unknown>) {
+function callToolBody(tool: string, args: Record<string, unknown>) {
   return {
     jsonrpc: "2.0",
-    id: 1,
+    id: Date.now(),
     method: "tools/call",
     params: {
       name: tool,
       arguments: args,
     },
   };
 }

1-16: Consider validating the response shape with zod for safer client parsing.

Given the broader codebase’s use of zod, add a schema for the expected tool call response and parse it here for runtime safety, especially as this function is public and may be reused. If helpful, I can propose a schema matching your MCP tool response format.

packages/cli-v3/src/utilities/configFiles.ts (1)

14-14: Typo in constant name

The constant name has a typo: DEFFAULT_PROFILE should be DEFAULT_PROFILE.

-export const DEFFAULT_PROFILE = "default";
+export const DEFAULT_PROFILE = "default";

Remember to update all references to this constant throughout the file.

packages/cli-v3/src/commands/install-mcp.ts (1)

19-21: Consider deriving cliTag from a more robust version check

The current logic only checks for "v4-beta" in the version string. Consider using a more robust version parsing approach to handle different version formats.

 const cliVersion = VERSION as string;
-const cliTag = cliVersion.includes("v4-beta") ? "v4-beta" : "latest";
+const cliTag = cliVersion.includes("v4-beta") ? "v4-beta" 
+  : cliVersion.includes("alpha") ? "alpha"
+  : cliVersion.includes("beta") ? "beta"
+  : "latest";
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)

48-50: Remove unnecessary null check

The findMany method always returns an array (possibly empty), never null. The null check is unnecessary.

-  if (!projects) {
-    return json({ error: "Projects not found" }, { status: 404 });
-  }
+  if (projects.length === 0) {
+    return json({ error: "No projects found" }, { status: 404 });
+  }
packages/cli-v3/src/mcp/auth.ts (1)

120-128: Long polling timeout may cause poor user experience.

The polling configuration uses 60 retries with 1-second intervals, resulting in a maximum wait time of 60 seconds. This might be too long for users who accidentally close the browser or encounter network issues.

Consider adding a more informative retry mechanism with progress feedback:

   const indexResult = await pRetry(
     () => getPersonalAccessToken(apiClient, authorizationCodeResult.authorizationCode),
     {
       //this means we're polling, same distance between each attempt
       factor: 1,
-      retries: 60,
+      retries: 30, // Reduce to 30 seconds total
       minTimeout: 1000,
+      onFailedAttempt: (error) => {
+        // Optionally log progress if in verbose mode
+        context.logger?.log(`Waiting for authorization... (attempt ${error.attemptNumber})`);
+      }
     }
   );
packages/cli-v3/src/mcp/tools.ts (1)

703-707: Error swallowing in cancel_run could hide important context.

Using tryCatch and only extracting the error message might lose important error details like stack traces or error codes that could be helpful for debugging.

Consider preserving more error context:

-  const [cancelError] = await tryCatch(apiClient.cancelRun(runId));
-
-  if (cancelError) {
-    return respondWithError(cancelError.message);
-  }
+  try {
+    await apiClient.cancelRun(runId);
+  } catch (error) {
+    context.logger?.log("Failed to cancel run", { runId, error });
+    return respondWithError(error);
+  }
packages/core/src/v3/schemas/api.ts (1)

1143-1162: Consistent pagination parameter naming between options and search params

The same pagination parameters have different names in ApiDeploymentListOptions (uses cursor and limit) vs ApiDeploymentListSearchParams (uses "page[after]" and "page[size]"). This dual naming convention could lead to confusion.

Consider documenting why these different naming conventions are used (e.g., internal vs external API), or add a comment explaining the mapping between them.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd6a0b3 and 7aefff6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .cursor/mcp.json (1 hunks)
  • .gitignore (1 hunks)
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (5 hunks)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.orgs.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (2 hunks)
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1 hunks)
  • apps/webapp/app/services/authorization.server.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/install-mcp.sh (1 hunks)
  • packages/cli-v3/package.json (2 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/cli/common.ts (1 hunks)
  • packages/cli-v3/src/cli/index.ts (2 hunks)
  • packages/cli-v3/src/commands/deploy.ts (1 hunks)
  • packages/cli-v3/src/commands/dev.ts (2 hunks)
  • packages/cli-v3/src/commands/init.ts (9 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/login.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/dev/devOutput.ts (2 hunks)
  • packages/cli-v3/src/mcp/auth.ts (1 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/logger.ts (1 hunks)
  • packages/cli-v3/src/mcp/mintlifyClient.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • packages/core/src/v3/apiClient/index.ts (3 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/commands/deploy.ts
  • packages/cli-v3/src/dev/devOutput.ts
  • packages/cli-v3/src/mcp/utils.ts
  • packages/cli-v3/src/cli/common.ts
  • packages/cli-v3/src/mcp/logger.ts
  • packages/cli-v3/src/mcp/capabilities.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • packages/cli-v3/src/commands/login.ts
  • packages/cli-v3/src/commands/dev.ts
  • packages/cli-v3/src/mcp/context.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v1.runs.ts
  • packages/cli-v3/src/mcp/tools.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/cli-v3/src/mcp/mintlifyClient.ts
  • packages/cli-v3/src/mcp/auth.ts
  • packages/core/src/v3/apiClient/index.ts
  • packages/cli-v3/src/mcp/schemas.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • packages/cli-v3/src/commands/mcp.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • packages/cli-v3/src/cli/index.ts
  • packages/cli-v3/src/apiClient.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/utilities/fileSystem.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/cli-v3/src/commands/install-mcp.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
  • packages/cli-v3/src/commands/init.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v1.runs.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/core/src/v3/apiClient/index.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v1.runs.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (6)
📚 Learning: 2025-07-18T17:49:47.180Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-07-18T17:49:47.180Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : In the webapp, all environment variables must be accessed through the `env` export of `env.server.ts`, instead of directly accessing `process.env`.

Applied to files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
📚 Learning: 2025-07-18T17:49:47.180Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-07-18T17:49:47.180Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.ts : Favor the use of 'presenters' to move complex loader code into a class, as seen in `app/v3/presenters/**/*.server.ts`.

Applied to files:

  • apps/webapp/app/routes/api.v1.runs.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When implementing schema tasks, use `schemaTask` from `trigger.dev/sdk/v3` and validate payloads as shown.

Applied to files:

  • packages/cli-v3/src/mcp/schemas.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : ALWAYS generate Trigger.dev tasks using the `task` function from `trigger.dev/sdk/v3` and export them as shown in the correct pattern.

Applied to files:

  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : You MUST use `trigger.dev/sdk/v3` when writing Trigger.dev tasks.

Applied to files:

  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Before generating any code for Trigger.dev tasks, verify: (1) Are you importing from `trigger.dev/sdk/v3`? (2) Have you exported every task? (3) Have you generated any deprecated code patterns?

Applied to files:

  • packages/cli-v3/src/commands/init.ts
🧬 Code Graph Analysis (24)
packages/cli-v3/src/dev/devOutput.ts (2)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • chalkGrey (20-22)
packages/cli-v3/src/utilities/eventBus.ts (1)
  • EventBusEventArgs (24-24)
packages/cli-v3/src/mcp/capabilities.ts (1)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (12-24)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/presenters/v3/DevPresence.server.ts (1)
  • devPresence (29-36)
apps/webapp/app/routes/api.v1.orgs.ts (5)
apps/webapp/app/routes/api.v1.projects.ts (1)
  • loader (8-54)
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)
  • loader (18-67)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/core/src/v3/schemas/api.ts (2)
  • GetOrgsResponseBody (54-61)
  • GetOrgsResponseBody (63-63)
packages/cli-v3/src/commands/login.ts (1)
packages/cli-v3/src/apiClient.ts (1)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/commands/dev.ts (3)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • readConfigHasSeenMCPInstallPrompt (104-109)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (184-248)
packages/core/src/v3/index.ts (1)
  • VERSION (85-85)
packages/cli-v3/src/mcp/context.ts (1)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (6)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
  • createLoaderApiRoute (94-284)
apps/webapp/app/db.server.ts (1)
  • $replica (102-105)
packages/core/src/v3/isomorphic/friendlyId.ts (1)
  • BatchId (96-96)
apps/webapp/app/v3/eventRepository.server.ts (1)
  • eventRepository (1339-1339)
apps/webapp/app/v3/taskEventStore.server.ts (1)
  • getTaskEventStoreTableForRun (28-32)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/cli-v3/src/mcp/tools.ts (11)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (12-24)
packages/core/src/v3/schemas/api.ts (7)
  • GetProjectsResponseBody (50-50)
  • GetProjectsResponseBody (52-52)
  • GetOrgsResponseBody (54-61)
  • GetOrgsResponseBody (63-63)
  • RunStatus (703-730)
  • RunStatus (732-732)
  • ApiDeploymentListParams (1163-1167)
packages/cli-v3/src/mcp/auth.ts (2)
  • mcpAuth (24-153)
  • createApiClientWithPublicJWT (191-211)
packages/cli-v3/src/mcp/utils.ts (1)
  • respondWithError (3-13)
packages/cli-v3/src/apiClient.ts (1)
  • CliApiClient (55-763)
packages/cli-v3/src/mcp/mintlifyClient.ts (1)
  • performSearch (1-16)
packages/cli-v3/src/mcp/schemas.ts (1)
  • ProjectRefSchema (3-8)
packages/cli-v3/src/commands/update.ts (2)
  • tryResolveTriggerPackageVersion (331-367)
  • getPackageJson (413-420)
packages/core/src/v3/index.ts (1)
  • VERSION (85-85)
packages/cli-v3/src/mcp/capabilities.ts (1)
  • hasRootsCapability (3-11)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/mcp/mintlifyClient.ts (1)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/cli-v3/src/mcp/auth.ts (7)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (12-24)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/utilities/isPersonalAccessToken.ts (1)
  • NotPersonalAccessTokenError (7-12)
packages/cli-v3/src/apiClient.ts (3)
  • CliApiClient (55-763)
  • createAuthorizationCode (69-77)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readAuthConfigProfile (92-102)
  • writeAuthConfigProfile (81-90)
packages/cli-v3/src/commands/login.ts (1)
  • getPersonalAccessToken (349-378)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
packages/core/src/v3/apiClient/index.ts (2)
packages/core/src/v3/apiClient/core.ts (3)
  • ZodFetchOptions (31-39)
  • zodfetch (71-78)
  • zodfetchCursorPage (80-115)
packages/core/src/v3/schemas/api.ts (4)
  • ApiDeploymentListOptions (1169-1169)
  • ApiDeploymentListOptions (1171-1171)
  • ApiDeploymentListResponseItem (1181-1200)
  • ApiDeploymentListResponseItem (1202-1202)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (6)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (2)
  • loader (17-71)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/v3/models/workerDeployment.server.ts (1)
  • findCurrentWorkerFromEnvironment (198-224)
apps/webapp/app/db.server.ts (1)
  • $replica (102-105)
packages/core/src/v3/schemas/api.ts (2)
  • GetWorkerByTagResponse (90-99)
  • GetWorkerByTagResponse (101-101)
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (1)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • createPersonalAccessTokenFromAuthorizationCode (186-261)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (3)
apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (1)
  • action (15-95)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
packages/cli-v3/src/commands/mcp.ts (7)
packages/cli-v3/src/cli/common.ts (4)
  • CommonCommandOptions (12-17)
  • CommonCommandOptions (19-19)
  • commonOptions (21-31)
  • wrapCommandAction (45-82)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (184-248)
packages/cli-v3/src/mcp/context.ts (2)
  • logger (21-23)
  • McpContext (12-24)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/mcp/tools.ts (9)
  • registerSearchDocsTool (185-203)
  • registerInitializeProjectTool (205-277)
  • registerTriggerTaskTool (348-521)
  • registerGetRunDetailsTool (523-629)
  • registerCancelRunTool (631-718)
  • registerListRunsTool (720-870)
  • registerDeployTool (872-1011)
  • registerListDeploymentsTool (1013-1109)
  • registerListPreviewBranchesTool (1111-1167)
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (5)
apps/webapp/app/routes/api.v1.orgs.ts (1)
  • loader (7-37)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/core/src/v3/schemas/api.ts (6)
  • GetProjectsResponseBody (50-50)
  • GetProjectsResponseBody (52-52)
  • CreateProjectRequestBody (65-67)
  • CreateProjectRequestBody (69-69)
  • GetProjectResponseBody (30-46)
  • GetProjectResponseBody (48-48)
packages/cli-v3/src/apiClient.ts (1)
  • createProject (158-168)
apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (3)
apps/webapp/app/routes/api.v1.runs.ts (1)
  • loader (9-34)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (4)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
  • createActionApiRoute (503-748)
apps/webapp/app/db.server.ts (1)
  • $replica (102-105)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/v3/services/cancelTaskRun.server.ts (1)
  • CancelTaskRunService (26-72)
packages/cli-v3/src/cli/index.ts (2)
packages/cli-v3/src/commands/mcp.ts (1)
  • configureMcpCommand (39-55)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • configureInstallMcpCommand (122-157)
packages/cli-v3/src/apiClient.ts (2)
packages/core/src/v3/apiClient/core.ts (1)
  • wrapZodFetch (727-767)
packages/core/src/v3/schemas/api.ts (14)
  • GetOrgsResponseBody (54-61)
  • GetOrgsResponseBody (63-63)
  • CreateProjectRequestBody (65-67)
  • CreateProjectRequestBody (69-69)
  • GetProjectResponseBody (30-46)
  • GetProjectResponseBody (48-48)
  • GetWorkerByTagResponse (90-99)
  • GetWorkerByTagResponse (101-101)
  • GetJWTRequestBody (103-110)
  • GetJWTRequestBody (112-112)
  • GetJWTResponse (114-116)
  • GetJWTResponse (118-118)
  • ApiBranchListResponseBody (1204-1215)
  • ApiBranchListResponseBody (1217-1217)
packages/cli-v3/src/commands/install-mcp.ts (6)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • writeConfigHasSeenMCPInstallPrompt (111-117)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
packages/cli-v3/src/utilities/fileSystem.ts (5)
  • expandTilde (54-68)
  • safeReadJSONCFile (139-147)
  • writeJSONFile (90-92)
  • safeReadTomlFile (125-133)
  • writeTomlFile (135-137)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
packages/cli-v3/src/commands/init.ts (7)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • readConfigHasSeenMCPInstallPrompt (104-109)
packages/core/src/v3/utils/structuredLogger.ts (1)
  • log (38-42)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (184-248)
packages/cli-v3/src/cli/common.ts (1)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
packages/trigger-sdk/src/v3/index.ts (1)
  • LogLevel (40-40)
packages/core/src/v3/index.ts (1)
  • LogLevel (40-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (35)
.gitignore (1)

66-67: LGTM: Ignore MCP runtime log

Adding .mcp.log to .gitignore is aligned with the new MCP workflows and prevents noisy log commits. No concerns.

packages/cli-v3/src/commands/deploy.ts (1)

71-71: LGTM: Updated command description

The description change is accurate and user-friendly. No functional impact.

apps/webapp/app/routes/api.v1.projects.ts (1)

1-1: LGTM: Type import expanded for action-support

Including ActionFunctionArgs keeps this route consistent with the broader move to action-based handlers. No runtime changes or issues.

packages/cli-v3/src/dev/devOutput.ts (1)

44-49: Terminology alignment looks good

Renaming “background worker” to “local worker” in logs is consistent with the rest of the MCP/CLI v3 work. No behavioral impact.

Also applies to: 92-92

packages/cli-v3/src/mcp/utils.ts (1)

3-13: LGTM! Well-structured error handling utility.

The function provides a clean, standardized way to convert errors into MCP CallToolResult format with proper error indication.

packages/cli-v3/src/commands/update.ts (4)

4-4: LGTM! Import addition aligns with function signature change.

The addition of join to the path imports supports the updated parameter handling in the exported functions.


296-329: Function visibility change is appropriate.

Converting getTriggerDependencies from internal to exported function enables reuse across the codebase while maintaining the same functionality.


322-322: LGTM! Parameter change improves function clarity.

The change from packageJsonPath to dirname(packageJsonPath) makes the function signature more intuitive by accepting a base directory rather than a specific file path.


331-367: Function refactoring improves API design.

The parameter change from packageJsonPath to basedir makes the function more generic and reusable, clearly indicating it expects a directory path rather than a file path.

packages/cli-v3/package.json (3)

79-80: LGTM! New MCP-related scripts are well-structured.

The addition of install-mcp and inspector scripts properly supports the new MCP functionality with appropriate file references and configuration.


104-104: LGTM! New dependency supports enhanced file handling.

The addition of confbox enables TOML/JSONC configuration file handling, which aligns with the MCP functionality mentioned in the AI summary.


83-83: Verify upstream breaking changes for @clack/prompts 0.11.0

I couldn’t locate a 0.11.0 changelog entry in the local codebase—please manually check the upstream release notes to ensure no breaking changes were introduced when moving from ^0.10.0 to exactly 0.11.0:

• Review the changelog or “Releases” on https://github.com/bombshell-dev/clack/tree/main/packages/prompts
• Confirm there are no API or behavior changes affecting our usage of prompts
• Ensure pinning to 0.11.0 doesn’t block any needed patch updates in the future

apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (4)

18-22: LGTM! Schema definition supports flexible parameter parsing.

The SearchParamsSchema correctly defines optional parameters with appropriate types for parsing URL search parameters.


108-114: LGTM! Client name mapping improves user experience.

The prettyClientNames mapping provides user-friendly display names for various MCP clients, enhancing the user interface.


116-124: LGTM! Context-aware instruction generation.

The function provides appropriate instructions based on the source and client, improving user guidance for different authentication flows.


51-53: LGTM! Consistent payload structure across success and error cases.

The addition of source and clientName to both success and error response payloads ensures consistent data availability for the UI components.

Also applies to: 63-65

packages/cli-v3/src/cli/index.ts (1)

39-40: LGTM: MCP commands are correctly wired into the CLI.

The new mcp and install-mcp commands are registered consistently with the rest of the CLI commands.

apps/webapp/app/routes/api.v1.orgs.ts (2)

3-3: Correct subpath import for core types.

Importing GetOrgsResponseBody from @trigger.dev/core/v3 adheres to the webapp guideline to avoid root imports.


25-27: Prune unreachable null check and standardize list semantics

The if (!orgs) block in apps/webapp/app/routes/api.v1.orgs.ts is unreachable since prisma.organization.findMany() always returns an array. We should remove this dead branch and decide on one of two consistent behaviors for empty results across all list endpoints:

• Remove the null check entirely and return 200 with an empty array (preferred REST convention).
• Change to if (orgs.length === 0) and return 404 for empty results if that aligns with other routes.

Suggested diff:

 apps/webapp/app/routes/api.v1.orgs.ts
   const orgs = await prisma.organization.findMany({
     where: { /* … */ }
   });

-  if (!orgs) {
-    return json({ error: "Orgs not found" }, { status: 404 });
-  }
+
+  // findMany() always returns an array—remove unreachable null check

Which behavior should we standardize on:

  1. 200 + [] for no orgs, or
  2. 404 when orgs.length === 0?
packages/cli-v3/src/mcp/mintlifyClient.ts (1)

4-12: Make MCP endpoint and protocol version configurable & add error handling

File: packages/cli-v3/src/mcp/mintlifyClient.ts (around lines 4–12)

  • Extract the hardcoded URL and protocol header into parameters or shared constants (ideally loaded from your CLI config).
  • Add a check on response.ok and throw a clear error if the request fails.
  • Allow callers to override headers (e.g. for custom auth or tracing).

Suggested diff:

 export async function performSearch(
-  query: string
+  query: string,
+  options?: {
+    endpoint?: string;
+    protocolVersion?: string;
+    headers?: Record<string, string>;
+  }
 ) {
   const body = callToolBody("search", { query });

-  const response = await fetch("https://trigger.dev/docs/mcp", {
+  const endpoint = options?.endpoint ?? DEFAULT_MCP_ENDPOINT;
+  const protocolVersion = options?.protocolVersion ?? DEFAULT_MCP_PROTOCOL_VERSION;
+  const response = await fetch(endpoint, {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
-      Accept: "application/json, text/event-stream",
-      "MCP-Protocol-Version": "2025-06-18",
+      Accept: "application/json, text/event-stream",
+      "MCP-Protocol-Version": protocolVersion,
+      ...options?.headers,
     },
     body: JSON.stringify(body),
   });
+
+  if (!response.ok) {
+    let errMsg: string;
+    try {
+      const err = await response.json();
+      errMsg = typeof err === "string" ? err : JSON.stringify(err);
+    } catch {
+      errMsg = response.statusText || `HTTP ${response.status}`;
+    }
+    throw new Error(`MCP request failed: ${errMsg}`);
+  }

Could you confirm the official MCP endpoint and protocol-version header we should default to? Once we have the canonical values, we can pull them from a constant or user‐config instead of hardcoding.

apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (1)

97-174: LGTM! Well-structured API endpoint

The loader implementation follows the established patterns consistently:

  • Proper authentication and authorization checks
  • Clear error messages with appropriate HTTP status codes
  • Efficient database queries with minimal field selection
  • Clean data transformation for the API response
packages/cli-v3/src/mcp/context.ts (1)

1-24: LGTM! Clean and focused context implementation

The McpContext class provides a clean abstraction for MCP server context with appropriate encapsulation and a convenient logger accessor.

apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1)

14-61: LGTM! Consistent and well-implemented API endpoint

The loader follows the established patterns for API routes with proper authentication, validation, and error handling. The implementation efficiently checks the dev environment connection status.

apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (2)

1-7: LGTM!

Imports are well-organized and follow best practices with appropriate use of type imports for TypeScript types.


61-68: Verify correct use of the label argument in findCurrentWorkerFromEnvironment

You’re currently passing params.tagName as the third (“label”) parameter:

const currentWorker = await findCurrentWorkerFromEnvironment(
  { id: runtimeEnv.id, type: runtimeEnv.type },
  replica,
  params.tagName
);

However, that argument is used to filter deployments by a deployment label (it defaults to CURRENT_DEPLOYMENT_LABEL), not by the worker’s friendly ID/tag.
• If your goal is to fetch a worker by its friendly ID (tagName), consider using or creating a helper that queries by backgroundWorker.friendlyId.
• If you really intended to override the deployment label, pass the appropriate constant (e.g. CURRENT_DEPLOYMENT_LABEL or CURRENT_UNMANAGED_DEPLOYMENT_LABEL) and rename the variable for clarity.

Please confirm which behavior you need and update either the function call or the helper function accordingly.

packages/core/src/v3/apiClient/index.ts (2)

345-355: LGTM!

The retrieveRunTrace method is correctly implemented with proper error handling and request configuration.


978-1011: listDeployments is defined only once – no duplicates detected

The listDeployments method appears a single time in packages/core/src/v3/apiClient/index.ts. No duplicate definitions exist, so no changes are needed here.

Likely an incorrect or invalid review comment.

packages/cli-v3/src/commands/install-mcp.ts (1)

546-552: Retain the array‐format command for local MCP servers

The Opencode docs specify that for local MCP servers you should provide command as an array of strings (e.g. ["bun", "x", "my-mcp-command"]) rather than separating it into command and args. Your existing implementation:

case "opencode": {
  return {
    type: "local",
    command: ["npx", ...args],
    enabled: true,
  };
}

matches the recommended format and should remain unchanged. Refer to the “Local MCP server” example in the official docs: https://opencode.ai/docs/mcp-servers/

Likely an incorrect or invalid review comment.

packages/cli-v3/src/apiClient.ts (1)

158-168: LGTM! Well-structured project creation method.

The createProject method follows the established pattern with proper error handling and consistent use of the authorization header.

packages/cli-v3/src/mcp/auth.ts (1)

30-62: LGTM! Robust environment-based authentication flow.

The implementation correctly validates personal access tokens and handles environment variables with appropriate fallback chains for API URL resolution.

packages/cli-v3/src/mcp/tools.ts (1)

983-990: No shell-injection risk here: tinyexec’s x uses spawn and your env block replaces (not merges) the parent environment

The deployProcess call in packages/cli-v3/src/mcp/tools.ts only passes a single environment variable (TRIGGER_MCP_SERVER), so no other process.env values are inherited. Node’s child_process.spawn (which tinyexec wraps) does not invoke a shell and, when given an env option, uses exactly those vars—there’s no unchecked input or fallback to process.env.

The only place we saw ...process.env is in internal testcontainers utilities, which is intentional for testing purposes. You can safely ignore the suggestion to filter environment variables in the deploy command.

Likely an incorrect or invalid review comment.

packages/cli-v3/src/commands/init.ts (4)

117-119: Good validation for --yes flag requirements

The validation correctly ensures that --project-ref is required when using the --yes flag, providing a clear error message to users.


504-539: Well-structured outputter implementation

The CLIInstallPackagesOutputter class provides a clean separation of concerns for handling CLI output during package installation. The implementation correctly handles different log levels and provides appropriate user feedback.


549-574: Excellent API design for package installation

The refactored installPackages function with the InstallPackagesOutputter interface provides a clean, testable API that follows the dependency injection pattern. The default silent outputter is a good choice for programmatic usage.


287-322: Clean implementation of --yes flag behavior

The conditional logic for handling the --yes flag in createTriggerDir is well-implemented, providing sensible defaults while preserving the interactive experience when the flag is not used.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

♻️ Duplicate comments (7)
packages/core/src/v3/schemas/api.ts (2)

80-88: Export missing type and narrow payloadSchema to a concrete schema

The TS type for GetWorkerTaskResponse is not exported, and payloadSchema can be narrowed for consistency with the rest of the file.

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
-  payloadSchema: z.any().nullish(),
+  payloadSchema: DeserializedJsonSchema.nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

1163-1169: Clarify naming: distinguish plain shape from Zod schema

This was previously flagged and still applies. ApiDeploymentListParams is a plain object used as input to z.object(), so name it accordingly to avoid confusion.

-export const ApiDeploymentListParams = {
+const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

Note: If any external code imports ApiDeploymentListParams today, this rename is breaking. Search usages and update them accordingly.

Also applies to: 1169-1172

packages/cli-v3/src/mcp/logger.ts (1)

14-23: Add error handling for file operations.

The current implementation doesn't handle potential file system errors that could occur during logging operations. This is a duplicate of a previous review comment that should be addressed.

Apply this diff to add error handling:

  log(message: string, ...args: unknown[]) {
    const logMessage = `[${new Date().toISOString()}][${this.formatServerInfo()}] ${message} - ${util.inspect(
      args,
      {
        depth: null,
        colors: false,
      }
    )}\n`;
-   appendFileSync(this.filePath, logMessage);
+   try {
+     appendFileSync(this.filePath, logMessage);
+   } catch (error) {
+     console.error(`Failed to write to log file ${this.filePath}:`, error);
+   }
  }
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

702-714: Add 404 guard in action routes when findResource is provided.

The action handler never checks for a missing resource, so handler may receive resource = undefined and crash if it expects a non-null value. We should mirror the loader's 404 logic immediately after resolving resource.

Apply this diff to add the missing 404 guard:

       const resource = options.findResource
         ? await options.findResource(parsedParams, authenticationResult, parsedSearchParams)
         : undefined;
+
+      if (options.findResource && !resource) {
+        return await wrapResponse(
+          request,
+          json({ error: "Not found" }, { status: 404 }),
+          corsStrategy !== "none"
+        );
+      }

       const result = await handler({
packages/cli-v3/src/utilities/configFiles.ts (1)

111-117: Settings object is being replaced instead of merged

The writeConfigHasSeenMCPInstallPrompt function replaces the entire settings object, which will lose any other settings that might be added in the future.

Apply this fix to preserve existing settings:

 export function writeConfigHasSeenMCPInstallPrompt(hasSeenMCPInstallPrompt: boolean) {
   const config = getConfig();
-  config.settings = {
-    hasSeenMCPInstallPrompt,
-  };
+  config.settings = {
+    ...config.settings,
+    hasSeenMCPInstallPrompt,
+  };
   writeAuthConfigFile(config);
 }
packages/cli-v3/src/commands/install-mcp.ts (1)

386-412: Add validation for empty path components

The function checks for falsy values but continues silently, which could result in incorrect nesting structure if there are empty strings in the path components array.

 function applyConfigToExistingConfig(
   existingConfig: any,
   pathComponents: string[],
   config: McpServerConfig
 ) {
+  // Validate and filter empty path components
+  const validPathComponents = pathComponents.filter(component => component && component.trim());
+  
+  if (validPathComponents.length === 0) {
+    throw new Error("Invalid path components: no valid segments provided");
+  }
+  
   const clonedConfig = structuredClone(existingConfig);
 
   let currentValueAtPath = clonedConfig;
 
-  for (let i = 0; i < pathComponents.length; i++) {
-    const currentPathSegment = pathComponents[i];
-
-    if (!currentPathSegment) {
-      break;
-    }
+  for (let i = 0; i < validPathComponents.length; i++) {
+    const currentPathSegment = validPathComponents[i];
 
-    if (i === pathComponents.length - 1) {
+    if (i === validPathComponents.length - 1) {
       currentValueAtPath[currentPathSegment] = config;
       break;
     } else {
       currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
       currentValueAtPath = currentValueAtPath[currentPathSegment];
     }
   }
 
   return clonedConfig;
 }
packages/cli-v3/src/commands/init.ts (1)

140-157: Incomplete MCP installation flow

The MCP installation flow returns immediately after installation (successful or failed) without continuing with the regular init process. Users who choose MCP installation won't get their project initialized.

After successful MCP installation, the flow should continue with project initialization:

       const [installError] = await tryCatch(
         installMcpServer({
           yolo: false,
           tag: options.tag,
           logLevel: options.logLevel,
         })
       );
 
       if (installError) {
         outro(`Failed to install MCP server: ${installError.message}`);
         return;
       }
 
-      return;
+      // Continue with the initialization after successful MCP installation
+      log.info("MCP server installed successfully. Continuing with project initialization...");
     }
   }
🧹 Nitpick comments (9)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)

6-6: Avoid cross-route imports; extract shared logic to a service/util module.

Importing from another route module couples route bundling and increases risk of circular deps. Consider moving getEnvironmentFromEnv to a shared module (e.g., app/services/environments.server.ts) and import it from there in both routes.


8-10: Validate that projectRef is non-empty.

A blank projectRef should be rejected at the schema level to avoid unnecessary DB queries and clearer 400 errors.

Apply this diff:

-const ParamsSchema = z.object({
-  projectRef: z.string(),
-});
+const ParamsSchema = z.object({
+  projectRef: z.string().min(1, "projectRef is required"),
+});

29-40: Select only required fields from Prisma to reduce payload and surface area.

We only need id here. Selecting narrowly improves performance and avoids accidentally leaking/depending on other fields later.

Apply this diff:

-  const project = await prisma.project.findFirst({
-    where: {
+  const project = await prisma.project.findFirst({
+    select: { id: true },
+    where: {
       externalRef: projectRef,
       organization: {
         members: {
           some: {
             userId: authenticationResult.userId,
           },
         },
       },
     },
   });

14-61: Consider using the new API route-builder with resource binding for consistency.

Per the broader PR goals (route-builder + findResource), this route looks like a good candidate to centralize auth, project resolution, and error handling to keep responses uniform across the API.

If you’d like, I can sketch a route-builder version of this loader.

packages/core/src/v3/schemas/api.ts (3)

54-61: Validate org slug shape

Slug values usually have a constrained charset. Consider validating it explicitly for consistency across APIs.

-    slug: z.string(),
+    slug: z.string().regex(/^[a-z0-9-]+$/, "Lowercase letters, digits and hyphens only"),

103-111: Standardize expirationTime and clarify claims defaults

To align with existing time handling (see TimePeriod at Line 998), consider reusing TimePeriod for expirationTime. Also, if claims is omitted, scopes never defaults; decide whether you want claims to default to { scopes: [] } or remain undefined.

Proposed change if you want both consistency and a default for claims:

-export const GetJWTRequestBody = z.object({
-  claims: z
-    .object({
-      scopes: z.array(z.string()).default([]),
-    })
-    .optional(),
-  expirationTime: z.union([z.number(), z.string()]).optional(),
-});
+export const GetJWTRequestBody = z.object({
+  claims: z
+    .object({
+      scopes: z.array(z.string()).default([]),
+    })
+    .default({ scopes: [] })
+    .optional(),
+  // Prefer reusing TimePeriod for consistency unless numeric seconds are required.
+  expirationTime: TimePeriod.optional(),
+});

If numeric seconds are required for expirationTime, keep the union but consider validating string durations with a regex like /^\\d+[smhdw]$/.


1143-1151: Consider stronger validation for date range filters

from and to are documented as ISO 8601; add validation to prevent silent acceptance of malformed inputs. Optionally, enforce mutual exclusivity with period at the options object level.

Possible tightening:

-  from: z.string().describe("The date to start the search from, in ISO 8601 format").optional(),
-  to: z.string().describe("The date to end the search, in ISO 8601 format").optional(),
+  from: z.string().datetime({ offset: true }).describe("ISO 8601").optional(),
+  to: z.string().datetime({ offset: true }).describe("ISO 8601").optional(),

And add a refinement on ApiDeploymentListOptions (see comment on Lines 1163-1172) to ensure period is not used together with from/to.

packages/cli-v3/src/mcp/schemas.ts (1)

3-8: Consider adding pattern validation for the project ref format.

Since the description states that project refs start with "proj_", consider adding a regex pattern validation to enforce this format programmatically.

export const ProjectRefSchema = z
  .string()
+  .regex(/^proj_/, 'Project ref must start with "proj_"')
  .describe(
    "The trigger.dev project ref, starts with proj_. We will attempt to automatically detect the project ref if running inside a directory that includes a trigger.config.ts file, or if you pass the --project-ref option to the MCP server."
  )
  .optional();
packages/cli-v3/src/commands/init.ts (1)

551-576: Consider adding package manager detection for better error messages

The new installPackages function removes package manager detection. Consider adding it back to provide more specific error messages when installations fail.

You could enhance error handling by detecting the package manager:

 export async function installPackages(
   projectDir: string,
   tag: string,
   outputter: InstallPackagesOutputter = new SilentInstallPackagesOutputter()
 ) {
   try {
     outputter.startSDK();
 
     await addDependency(`@trigger.dev/sdk@${tag}`, { cwd: projectDir, silent: true });
 
     outputter.installedSDK();
 
     outputter.startBuild();
 
     await addDevDependency(`@trigger.dev/build@${tag}`, {
       cwd: projectDir,
       silent: true,
     });
 
     outputter.installedBuild();
   } catch (e) {
     outputter.stoppedWithError();
+    
+    // Provide a hint about package manager issues
+    if (e instanceof Error && e.message.includes('ENOENT')) {
+      throw new Error(`Package installation failed. Make sure you have a package manager installed (npm, yarn, pnpm, or bun).`);
+    }
 
     throw e;
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aefff6 and a510fce.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .cursor/mcp.json (1 hunks)
  • .gitignore (1 hunks)
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (5 hunks)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.orgs.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (2 hunks)
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1 hunks)
  • apps/webapp/app/services/authorization.server.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/install-mcp.sh (1 hunks)
  • packages/cli-v3/package.json (2 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/cli/common.ts (1 hunks)
  • packages/cli-v3/src/cli/index.ts (2 hunks)
  • packages/cli-v3/src/commands/deploy.ts (1 hunks)
  • packages/cli-v3/src/commands/dev.ts (2 hunks)
  • packages/cli-v3/src/commands/init.ts (9 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/login.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/dev/devOutput.ts (2 hunks)
  • packages/cli-v3/src/mcp/auth.ts (1 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/logger.ts (1 hunks)
  • packages/cli-v3/src/mcp/mintlifyClient.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • packages/core/src/v3/apiClient/index.ts (3 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (33)
  • packages/cli-v3/src/mcp/utils.ts
  • packages/cli-v3/src/commands/deploy.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • packages/cli-v3/src/cli/common.ts
  • packages/cli-v3/src/mcp/context.ts
  • packages/cli-v3/src/dev/devOutput.ts
  • packages/cli-v3/src/mcp/mintlifyClient.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • packages/cli-v3/src/apiClient.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • packages/cli-v3/src/mcp/tools.ts
  • packages/cli-v3/package.json
  • packages/cli-v3/install-mcp.sh
  • .gitignore
  • packages/cli-v3/src/mcp/capabilities.ts
  • packages/cli-v3/src/commands/mcp.ts
  • .cursor/mcp.json
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/cli-v3/src/commands/dev.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • apps/webapp/app/routes/api.v1.runs.ts
  • packages/cli-v3/src/commands/update.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • packages/cli-v3/src/commands/login.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • packages/cli-v3/src/utilities/fileSystem.ts
  • packages/cli-v3/src/cli/index.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/apiClient/index.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/auth.ts
  • packages/cli-v3/src/mcp/schemas.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/cli-v3/src/mcp/logger.ts
  • packages/cli-v3/src/commands/init.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/cli-v3/src/commands/install-mcp.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (5)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When implementing schema tasks, use `schemaTask` from `trigger.dev/sdk/v3` and validate payloads as shown.

Applied to files:

  • packages/cli-v3/src/mcp/schemas.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : You MUST use `trigger.dev/sdk/v3` when writing Trigger.dev tasks.

Applied to files:

  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : ALWAYS generate Trigger.dev tasks using the `task` function from `trigger.dev/sdk/v3` and export them as shown in the correct pattern.

Applied to files:

  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Before generating any code for Trigger.dev tasks, verify: (1) Are you importing from `trigger.dev/sdk/v3`? (2) Have you exported every task? (3) Have you generated any deprecated code patterns?

Applied to files:

  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Tasks must be exported, even subtasks in the same file.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
🧬 Code Graph Analysis (5)
packages/cli-v3/src/mcp/auth.ts (9)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (12-24)
packages/cli-v3/src/utilities/session.ts (2)
  • LoginResult (19-28)
  • LoginResultOk (7-17)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/utilities/isPersonalAccessToken.ts (1)
  • NotPersonalAccessTokenError (7-12)
packages/cli-v3/src/apiClient.ts (3)
  • CliApiClient (55-763)
  • createAuthorizationCode (69-77)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readAuthConfigProfile (92-102)
  • writeAuthConfigProfile (81-90)
packages/cli-v3/src/commands/login.ts (1)
  • getPersonalAccessToken (349-378)
packages/core/src/v3/apiClientManager/index.ts (1)
  • client (57-63)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/presenters/v3/DevPresence.server.ts (1)
  • devPresence (29-36)
packages/cli-v3/src/commands/init.ts (7)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readConfigHasSeenMCPInstallPrompt (104-109)
  • writeConfigHasSeenMCPInstallPrompt (111-117)
packages/core/src/v3/utils/structuredLogger.ts (1)
  • log (38-42)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (184-248)
packages/cli-v3/src/cli/common.ts (1)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
packages/trigger-sdk/src/v3/index.ts (1)
  • LogLevel (40-40)
packages/core/src/v3/index.ts (1)
  • LogLevel (40-40)
packages/cli-v3/src/commands/install-mcp.ts (5)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • writeConfigHasSeenMCPInstallPrompt (111-117)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (5)
  • expandTilde (54-68)
  • safeReadJSONCFile (139-147)
  • writeJSONFile (90-92)
  • safeReadTomlFile (125-133)
  • writeTomlFile (135-137)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
🔇 Additional comments (23)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1)

14-61: LGTM overall; clear auth + membership checks and least-information error messaging.

The flow is straightforward and secure-by-default: PAT auth, org membership gating, dev env resolution, then presence check. Consistent JSON error shapes are good.

packages/core/src/v3/schemas/api.ts (7)

63-63: LGTM on type export

Type alias for GetOrgsResponseBody is correctly exported.


69-70: LGTM on type export

Type alias for CreateProjectRequestBody is correctly exported.


101-101: LGTM on type export

Type alias for GetWorkerByTagResponse is correctly exported.


114-119: LGTM

GetJWTResponse shape is simple and correct.


1173-1178: LGTM

Search-params variant mirrors the options shape with serialized names. With the updated limit constraints, this will inherit the bounds.


1202-1203: LGTM on type export

Type alias for ApiDeploymentListResponseItem is correctly exported.


1217-1218: LGTM on type export

Type alias for ApiBranchListResponseBody is correctly exported.

packages/cli-v3/src/mcp/logger.ts (6)

1-3: LGTM! Good use of Node.js modules with proper namespace import.

The imports are correctly using Node.js built-in modules with the node: prefix and the MCP SDK. The use of namespace import for util aligns with TypeScript best practices.


5-12: LGTM! Clean class structure with proper typing.

The class follows TypeScript conventions with private fields and a straightforward constructor. The dependency on McpServer is appropriately injected rather than instantiated internally.


25-27: LGTM! Well-structured server info formatting.

The method properly delegates to individual formatting methods, creating a clean separation of concerns.


29-32: LGTM! Proper null handling with fallback value.

The method correctly handles potential null/undefined values from the client version and provides a sensible fallback.


34-38: LGTM! Consistent error handling pattern.

The method follows the same null-safe pattern as formatClientName() with appropriate fallback handling.


40-46: LGTM! Robust capabilities formatting.

The method safely handles potential null/undefined capabilities and properly extracts and joins the keys. The empty object fallback ensures Object.keys() always receives a valid object.

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (6)

28-69: LGTM! Excellent type-safe resource binding implementation.

The addition of generic TResource parameter and findResource function to ApiKeyRouteBuilderOptions provides clean, type-safe resource binding. The conditional typing for params and searchParams based on schema types is well-designed, and the authorization integration properly uses NonNullable<TResource> to ensure resources are available when needed.


71-92: LGTM! Consistent handler function signature.

The ApiKeyHandlerFunction correctly includes the TResource generic parameter and adds the resource property to the handler arguments. The use of NonNullable<TResource> ensures type safety when resources are required.


189-200: LGTM! Proper resource validation in loader.

The loader correctly validates that the resource exists and returns a 404 with appropriate retry headers when not found. This follows good API design patterns.


429-475: LGTM! Consistent action route options.

The ApiKeyActionRouteBuilderOptions properly extends with TResource generic and includes the optional findResource function. The type definitions are consistent with the loader implementation.


477-501: LGTM! Action handler function signature is correct.

The ApiKeyActionHandlerFunction includes the TResource generic and adds the optional resource property to maintain consistency with the loader pattern.


503-524: LGTM! Function signature properly genericized.

The createActionApiRoute function correctly includes all generic parameters and type constraints for the new resource binding functionality.

packages/cli-v3/src/mcp/auth.ts (2)

90-94: Potentially undefined apiUrl passed to CliApiClient

When both authConfig and authConfig.apiUrl are undefined, the CliApiClient constructor receives undefined, which may cause issues if the constructor doesn't handle undefined gracefully.

-  const apiClient = new CliApiClient(authConfig?.apiUrl ?? opts.defaultApiUrl);
+  const apiUrl = authConfig?.apiUrl ?? opts.defaultApiUrl;
+  const apiClient = new CliApiClient(apiUrl);

Likely an incorrect or invalid review comment.


170-189: Elicitation capability confirmed in MCP SDK
The MCP specification and official SDKs (TypeScript, C#, etc.) support server.elicitInput() via the standardized “elicitation” capability¹²³⁴. No changes are needed in this server code—just ensure your client:

  • Declares the elicitation capability in its initialize call (e.g., in ClientCapabilities).
  • Implements a handler for elicitation/create requests.

With those in place, your fallback logic and prompt flow will work as intended.

packages/cli-v3/src/commands/init.ts (1)

543-549: LGTM! Clean abstraction for output handling.

The SilentInstallPackagesOutputter provides a clean no-op implementation of the InstallPackagesOutputter interface, following good design patterns.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

646-653: Don’t treat valid falsy JSON values as invalid (e.g., 0, false, null)

safeJsonParse likely returns undefined on parse failure. The current truthiness check will erroneously reject valid JSON like 0 or false.

Apply this diff:

-        const rawParsedJson = safeJsonParse(rawBody);
-
-        if (!rawParsedJson) {
+        const rawParsedJson = safeJsonParse(rawBody);
+        if (rawParsedJson === undefined) {
           return await wrapResponse(
             request,
             json({ error: "Invalid JSON" }, { status: 400 }),
             corsStrategy !== "none"
           );
         }
♻️ Duplicate comments (18)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1)

110-113: Validate the response with Zod before returning

Ensures runtime conformance with the public schema.

-  // Optionally validate the response before returning (for type safety)
-  // WorkerResponseSchema.parse(response);
+  // Validate the response before returning (for type safety)
+  GetWorkerByTagResponse.parse(response);
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)

1-1: Standardize Remix server import to '@remix-run/node'

Use the Node entry for server-only loaders.

-import { json, type LoaderFunctionArgs } from "@remix-run/server-runtime";
+import { json, type LoaderFunctionArgs } from "@remix-run/node";

12-13: Remove the shadowing, unused type alias

Avoid confusing shadowing of the schema identifier.

-type ParamsSchema = z.infer<typeof ParamsSchema>;
+// removed: unused shadowing type alias

17-19: Add Cache-Control: no-store to error responses

Prevents caching of auth/validation/not-found responses.

-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store" } }
+    );
@@
-    return json({ error: "Invalid Params" }, { status: 400 });
+    return json(
+      { error: "Invalid Params" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );
@@
-  if (!project) {
-    return json({ error: "Project not found" }, { status: 404 });
-  }
+  if (!project) {
+    return json(
+      { error: "Project not found" },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!envResult.success) {
-    return json({ error: envResult.error }, { status: 404 });
-  }
+  if (!envResult.success) {
+    return json(
+      { error: envResult.error },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }

Also applies to: 23-25, 42-44, 52-54


58-61: Guard devPresence lookup and disable caching

Handle transient Redis outages and avoid caching volatile status.

-  const isConnected = await devPresence.isConnected(runtimeEnv.id);
-
-  return json({ isConnected });
+  try {
+    const isConnected = await devPresence.isConnected(runtimeEnv.id);
+    return json({ isConnected }, { headers: { "Cache-Control": "no-store" } });
+  } catch {
+    return json(
+      { error: "Dev presence service unavailable" },
+      { status: 503, headers: { "Cache-Control": "no-store" } }
+    );
+  }
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (2)

139-141: Use an official CUID validator instead of a brittle regex

Replace the regex with a library validator to avoid format drift.

-function isCuid(orgParam: string): boolean {
-  return /^[0-9A-HJ-NP-TV-Z]{25}$/.test(orgParam);
-}
+function isCuid(orgParam: string): boolean {
+  // Prefer Cuid2; switch to `cuid` if you're on v1
+  return validateCuid(orgParam);
+}

Add this import at the top of the file (outside the selected lines):

import { isCuid as validateCuid } from "@paralleldrive/cuid2";

40-41: Fix version casing mismatch ('V3' vs 'v3')

New projects are created with "v3"; filtering with "V3" will omit them.

-      version: "V3",
+      version: "v3",
packages/core/src/v3/schemas/api.ts (8)

32-36: Enforce externalRef format to match its description

Validate that it starts with proj_ to align with the documented contract.

-  externalRef: z
-    .string()
-    .describe(
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9]+$/, "Must start with proj_")
+    .describe(
       "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
     ),

65-67: Harden CreateProjectRequestBody.name

Trim, require non-empty, and cap length to prevent pathological inputs.

-export const CreateProjectRequestBody = z.object({
-  name: z.string(),
-});
+export const CreateProjectRequestBody = z.object({
+  name: z
+    .string()
+    .trim()
+    .min(1, "Name is required")
+    .max(100, "Name must be 100 characters or fewer"),
+});

Also applies to: 69-69


80-88: Export the GetWorkerTaskResponse TypeScript type

Consumers need the nested task type alongside the schema.

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

Also applies to: 89-89


90-99: Constrain engine to RunEngineVersion

Avoid free-form strings for engine; reuse the shared union.

 export const GetWorkerByTagResponse = z.object({
   worker: z.object({
     id: z.string(),
     version: z.string(),
-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),
     sdkVersion: z.string().nullish(),
     cliVersion: z.string().nullish(),
     tasks: z.array(GetWorkerTaskResponse),
   }),
 });

1158-1162: Enforce pagination bounds and default

Apply int, min/max, and default(20). Remove optional so default can apply.

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1)
+  .max(100)
+  .default(20)
+  .describe("The number of deployments to return, defaults to 20 (max 100)");

1163-1171: Clarify naming: shape vs schema for ApiDeploymentListParams

The current name suggests a schema but is a plain shape object. Rename for clarity.

-export const ApiDeploymentListParams = {
+const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

1181-1200: Use GitMeta for deployments git metadata

Unify git metadata typing with the shared schema.

   deployedAt: z.coerce.date().optional(),
-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),
   error: DeploymentErrorData.optional(),

1204-1216: Use GitMeta for branch git metadata

Consistent git typing across responses.

       updatedAt: z.coerce.date(),
-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),
       isPaused: z.boolean(),
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)

442-452: Optional findResource hook for actions is useful, but missing a 404 guard when no resource is found

Good addition. However, when provided, the action should mirror the loader behavior and short-circuit with a 404 if the resource is not found, instead of calling the handler with resource = undefined.

I’ve proposed the 404 guard at the call site below (Lines 702-705).


702-705: Add 404 guard when findResource is provided but returns no resource

Without this, handlers can unexpectedly receive resource = undefined, diverging from the loader semantics and risking runtime errors.

Apply this diff:

       const resource = options.findResource
         ? await options.findResource(parsedParams, authenticationResult, parsedSearchParams)
         : undefined;
+
+      // Mirror loader semantics: if a resource is required (findResource provided) but missing, 404.
+      if (options.findResource && !resource) {
+        return await wrapResponse(
+          request,
+          json({ error: "Not found" }, { status: 404 }),
+          corsStrategy !== "none"
+        );
+      }
packages/cli-v3/install-mcp.sh (1)

116-167: DRY the JSON mutation with a single helper and parameterize API URL + server name via env

The inline Node.js blocks are duplicated across all installers and interpolate shell vars into JS strings (fragile quoting). Extract a reusable updater that accepts config path + key, uses env vars, creates a backup, and centralizes logic. Also make API URL configurable via MCP_API_URL and the server name via MCP_SERVER_NAME. This consolidates maintenance, avoids quoting pitfalls, and adds safety.

Add shared configuration and the updater function just after the “Found CLI” logs:

 echo "✅ Found Node.js at: $NODE_PATH"
 echo "✅ Found CLI at: $CLI_PATH"
 
+# Allow overrides for API URL and server name
+API_URL="${MCP_API_URL:-http://localhost:3030}"
+SERVER_NAME="${MCP_SERVER_NAME:-triggerdev}"
+
+# Reusable JSON updater using env vars to avoid quoting issues
+update_json_config() {
+  local config_path="$1"     # path to JSON file
+  local root_key="$2"        # mcpServers | servers | mcp
+  local add_stdio="${3:-}"   # "true" to include { type: "stdio" }
+  local ensure_schema="${4:-}" # "true" to ensure Crush $schema
+
+  CONFIG_PATH="$config_path" \
+  ROOT_KEY="$root_key" \
+  ADD_STDIO="$add_stdio" \
+  ENSURE_SCHEMA="$ensure_schema" \
+  NODE_BIN="$NODE_PATH" \
+  CLI_BIN="$CLI_PATH" \
+  LOG_FILE="$MCP_LOG_FILE" \
+  API_URL="$API_URL" \
+  SERVER_NAME="$SERVER_NAME" \
+  node -e '
+    const fs = require("fs");
+    const cfgPath = process.env.CONFIG_PATH;
+    const rootKey = process.env.ROOT_KEY;      // "mcpServers" | "servers" | "mcp"
+    const addStdio = process.env.ADD_STDIO === "true";
+    const ensureSchema = process.env.ENSURE_SCHEMA === "true";
+    const nodeBin = process.env.NODE_BIN;
+    const cliBin = process.env.CLI_BIN;
+    const logFile = process.env.LOG_FILE;
+    const apiUrl = process.env.API_URL;
+    const serverName = process.env.SERVER_NAME || "triggerdev";
+
+    try {
+      let config = {};
+      try {
+        config = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
+      } catch {}
+
+      if (ensureSchema && !config["$schema"]) {
+        config["$schema"] = "https://charm.land/crush.json";
+      }
+
+      if (!config[rootKey]) config[rootKey] = {};
+      const entry = {
+        command: nodeBin,
+        args: [cliBin, "mcp", "--log-file", logFile, "--api-url", apiUrl],
+      };
+      if (addStdio) entry.type = "stdio";
+
+      config[rootKey][serverName] = entry;
+
+      try {
+        if (fs.existsSync(cfgPath)) fs.copyFileSync(cfgPath, cfgPath + ".bak");
+      } catch (e) {
+        console.warn("⚠️ Could not create backup:", e.message);
+      }
+
+      fs.writeFileSync(cfgPath, JSON.stringify(config, null, 2));
+      console.log("✅ Updated MCP config", { cfgPath, rootKey, serverName, apiUrl });
+    } catch (error) {
+      console.error("❌ Error updating configuration:", error.message);
+      process.exit(1);
+    }
+  '
+}

Refactor each installer to call the helper (and keep the friendly logs). Example changes:

Claude Code:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating Claude configuration..."
-
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating Claude configuration..."
+    update_json_config "$CLAUDE_CONFIG" "mcpServers"
+    echo ""
+    echo "📋 Claude Code Configuration:"
+    echo "   • Config file: $CLAUDE_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 Try typing @ in Claude Code and select \"$SERVER_NAME\" to get started."

Claude Desktop:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating Claude Desktop configuration..."
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating Claude Desktop configuration..."
+    update_json_config "$CLAUDE_DESKTOP_CONFIG" "mcpServers"
+    echo ""
+    echo "📋 Claude Desktop Configuration:"
+    echo "   • Config file: $CLAUDE_DESKTOP_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 You can now use Trigger.dev MCP commands in Claude Desktop."

Cursor:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating Cursor configuration..."
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating Cursor configuration..."
+    update_json_config "$CURSOR_CONFIG" "mcpServers"
+    echo ""
+    echo "📋 Cursor Configuration:"
+    echo "   • Config file: $CURSOR_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 You can now use Trigger.dev MCP commands in Cursor."

VS Code:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating VS Code configuration..."
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating VS Code configuration..."
+    update_json_config "$VSCODE_CONFIG" "servers"
+    echo ""
+    echo "📋 VS Code Configuration:"
+    echo "   • Config file: $VSCODE_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 You can now use Trigger.dev MCP commands in VS Code."

Crush:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating Crush configuration..."
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating Crush configuration..."
+    update_json_config "$CRUSH_CONFIG" "mcp" "true" "true"
+    echo ""
+    echo "📋 Crush Configuration:"
+    echo "   • Config file: $CRUSH_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 You can now use Trigger.dev MCP commands in Crush."

Windsurf:

-    # Use Node.js to manipulate the JSON
-    echo "🔧 Updating Windsurf configuration..."
-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    echo "🔧 Updating Windsurf configuration..."
+    update_json_config "$WINDSURF_CONFIG" "mcpServers"
+    echo ""
+    echo "📋 Windsurf Configuration:"
+    echo "   • Config file: $WINDSURF_CONFIG"
+    echo "   • Node.js path: $NODE_PATH"
+    echo "   • CLI path: $CLI_PATH"
+    echo ""
+    echo "💡 You can now use Trigger.dev MCP commands in Windsurf."

Notes:

  • This also removes the repeated unused require('path').
  • API URL becomes configurable via MCP_API_URL (default keeps localhost:3030).
  • Server name becomes configurable via MCP_SERVER_NAME and defaults to triggerdev.

Also applies to: 191-242, 266-317, 341-392, 416-471, 495-546

🧹 Nitpick comments (20)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (4)

1-1: Standardize Remix server import to '@remix-run/node'

Aligns with server-only route conventions and avoids adapter inconsistencies.

-import { json, type LoaderFunctionArgs } from "@remix-run/server-runtime";
+import { json, type LoaderFunctionArgs } from "@remix-run/node";

15-15: Remove the shadowing, unused type alias

This alias shadows the runtime schema identifier and isn’t used.

-type ParamsSchema = z.infer<typeof ParamsSchema>;
+// removed: unused shadowing type alias

61-68: Use the validated tagName from Zod, not raw params

Avoids passing an undefined label and enforces the validated param.

-  const currentWorker = await findCurrentWorkerFromEnvironment(
+  const currentWorker = await findCurrentWorkerFromEnvironment(
     {
       id: runtimeEnv.id,
       type: runtimeEnv.type,
     },
     $replica,
-    params.tagName
+    parsedParams.data.tagName
   );

20-22: Mark error responses as non-cacheable

Prevent intermediaries from caching volatile auth/lookup errors.

-  if (!authenticationResult) {
-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
-  }
+  if (!authenticationResult) {
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!parsedParams.success) {
-    return json({ error: "Invalid Params" }, { status: 400 });
-  }
+  if (!parsedParams.success) {
+    return json(
+      { error: "Invalid Params" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!project) {
-    return json({ error: "Project not found" }, { status: 404 });
-  }
+  if (!project) {
+    return json(
+      { error: "Project not found" },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!envResult.success) {
-    return json({ error: envResult.error }, { status: 404 });
-  }
+  if (!envResult.success) {
+    return json(
+      { error: envResult.error },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!currentWorker) {
-    return json({ error: "Worker not found" }, { status: 404 });
-  }
+  if (!currentWorker) {
+    return json(
+      { error: "Worker not found" },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }

Also applies to: 26-28, 45-47, 55-57, 70-72

apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (4)

1-2: Standardize Remix server imports to '@remix-run/node'

Server-only route; align imports with Node entry.

-import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/server-runtime";
-import { json } from "@remix-run/server-runtime";
+import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
+import { json } from "@remix-run/node";

27-28: Prefer safeParse for params to return 400 on invalid input

Avoids throwing 500s on bad params and matches other routes’ style.

-  const { orgParam } = ParamsSchema.parse(params);
+  const parsedParams = ParamsSchema.safeParse(params);
+  if (!parsedParams.success) {
+    return json(
+      { error: "Invalid Params" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );
+  }
+  const { orgParam } = parsedParams.data;

48-50: Remove unreachable 404 branch for findMany

prisma.findMany returns an array (empty when none). The falsy check never triggers.

-  if (!projects) {
-    return json({ error: "Projects not found" }, { status: 404 });
-  }
+  // Intentionally return an empty array when no projects are found

24-25: Mark error responses as non-cacheable

Add Cache-Control: no-store to all early error returns.

-  if (!authenticationResult) {
-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
-  }
+  if (!authenticationResult) {
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!authenticationResult) {
-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
-  }
+  if (!authenticationResult) {
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!organization) {
-    return json({ error: "Organization not found" }, { status: 404 });
-  }
+  if (!organization) {
+    return json(
+      { error: "Organization not found" },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!parsedBody.success) {
-    return json({ error: "Invalid request body" }, { status: 400 });
-  }
+  if (!parsedBody.success) {
+    return json(
+      { error: "Invalid request body" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );
+  }

Also applies to: 72-74, 90-92, 97-99

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)

706-715: Avoid passing resource when undefined to satisfy exact optional prop semantics and prevent never pitfalls

If findResource isn’t provided, TResource defaults to never. Passing resource (as undefined) into a resource?: TResource parameter can conflict with exactOptionalPropertyTypes and stricter TS settings. Only include the property when a resource exists.

Apply this diff:

-      const result = await handler({
-        params: parsedParams,
-        searchParams: parsedSearchParams,
-        headers: parsedHeaders,
-        body: parsedBody,
-        authentication: authenticationResult,
-        request,
-        resource,
-      });
+      const baseArgs = {
+        params: parsedParams,
+        searchParams: parsedSearchParams,
+        headers: parsedHeaders,
+        body: parsedBody,
+        authentication: authenticationResult,
+        request,
+      } as const;
+
+      const result = await handler(
+        resource === undefined ? baseArgs : { ...baseArgs, resource }
+      );

577-584: Ensure CORS consistency for 413 responses

This early return is the only action-path response that skips wrapResponse, which can drop CORS headers when corsStrategy !== "none".

Apply this diff:

-        if (!contentLength || parseInt(contentLength) > maxContentLength) {
-          return json({ error: "Request body too large" }, { status: 413 });
-        }
+        if (!contentLength || parseInt(contentLength) > maxContentLength) {
+          return await wrapResponse(
+            request,
+            json({ error: "Request body too large" }, { status: 413 }),
+            corsStrategy !== "none"
+          );
+        }
packages/cli-v3/install-mcp.sh (6)

3-3: Harden the script: add pipefail/undef guards and an error trap

set -e alone misses failures in pipes and unset vars. Add stricter shell options and a simple trap for better diagnostics.

-set -e  # Exit on error
+set -Eeuo pipefail  # Exit on error, fail pipelines, and treat unset vars as errors
+
+# Helpful error trap for line/command visibility
+trap 'echo "❌ Error on line $LINENO: $(sed -n "${LINENO}p" "$0")" >&2' ERR

69-75: Prefer command -v over which for portability/reliability

command -v is POSIX, built-in in most shells, and avoids parsing which output.

-# Get the absolute path to the node binary
-NODE_PATH=$(which node)
+# Get the absolute path to the node binary
+NODE_PATH="$(command -v node || true)"
 if [ -z "$NODE_PATH" ]; then

96-98: Unnecessary chmod on a JS module

You run the CLI with node <file>, so the JS file doesn’t need the executable bit. Drop this to avoid confusing file modes in VCS.

-# Ensure the CLI is executable
-chmod +x "$CLI_PATH"

160-160: Server name mismatch: message says “triggerdev”, but config key is “trigger”

If UI surfaces the server by key, this will confuse users. Align the config key and user message by using a single SERVER_NAME (default “triggerdev”).

The refactor above introduces SERVER_NAME and updates messages to reference it. If you prefer to keep “trigger”, update the messages accordingly instead.

Also applies to: 235-235, 311-311, 385-385, 465-465, 539-539


324-339: macOS-only VS Code path; add Linux/Windows fallbacks

The path targets macOS: ~/Library/Application Support/Code/User. On Linux it’s ~/.config/Code/User; on Windows it’s %APPDATA%\Code\User. Consider OS detection to improve developer ergonomics.

Example snippet:

# Before setting VSCODE_DIR
case "$(uname -s)" in
  Darwin) VSCODE_DIR="$HOME/Library/Application Support/Code/User" ;;
  Linux)  VSCODE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/Code/User" ;;
  CYGWIN*|MINGW*|MSYS*) VSCODE_DIR="$(cygpath "$APPDATA")/Code/User" ;;
  *) echo "⚠️ Unknown OS; defaulting to macOS path"; VSCODE_DIR="$HOME/Library/Application Support/Code/User" ;;
esac
VSCODE_CONFIG="$VSCODE_DIR/mcp.json"

15-16: Expose API URL override in help text

Since the script hardcodes http://localhost:3030, document the MCP_API_URL env override (if you adopt the refactor) so users can point to non-local servers.

-    echo "  -t, --target TARGET    Install target: claude, claude-desktop, cursor, vscode, crush, windsurf, or all (default: all)"
+    echo "  -t, --target TARGET    Install target: claude, claude-desktop, cursor, vscode, crush, windsurf, or all (default: all)"
+    echo "Env:"
+    echo "  MCP_API_URL            Override API URL (default: http://localhost:3030)"
+    echo "  MCP_SERVER_NAME        Override server name in configs (default: triggerdev)"
packages/cli-v3/src/mcp/utils.ts (1)

15-35: Make error serialization resilient (BigInt/undefined) and include common fields like code

Current logic will emit {} when error is undefined, and JSON.stringify will throw if error is a BigInt. Also, many error types expose a code (useful but not PII), which we can safely include. Small tweak below makes serialization robust and predictable.

Apply this diff to harden enumerateError and make the return type explicit:

-function enumerateError(error: unknown) {
-  if (!error) {
-    return error;
-  }
-
-  if (typeof error !== "object") {
-    return error;
-  }
-
-  const newError: Record<string, unknown> = {};
-
-  const errorProps = ["name", "message"] as const;
-
-  for (const prop of errorProps) {
-    if (prop in error) {
-      newError[prop] = (error as Record<string, unknown>)[prop];
-    }
-  }
-
-  return newError;
-}
+function enumerateError(error: unknown): unknown {
+  // null/undefined -> predictable message
+  if (error == null) {
+    return { message: "Unknown error" };
+  }
+
+  // BigInt throws in JSON.stringify; stringify scalars to keep output stable
+  if (typeof error === "bigint") {
+    return error.toString();
+  }
+  if (typeof error !== "object") {
+    return String(error);
+  }
+
+  const obj = error as Record<string, unknown>;
+  const newError: Record<string, unknown> = {};
+
+  // Keep safe, helpful fields
+  const errorProps = ["name", "message", "code"] as const;
+  for (const prop of errorProps) {
+    const value = obj[prop as string];
+    if (typeof value !== "undefined") {
+      newError[prop] = value;
+    }
+  }
+
+  // Fallback if no message available
+  if (typeof newError.message === "undefined") {
+    try {
+      newError.message = String(error);
+    } catch {
+      newError.message = "Unserializable error";
+    }
+  }
+
+  return newError;
+}
packages/cli-v3/src/mcp/auth.ts (3)

116-118: Guard the browser open call to avoid login flow abort on headless environments

open() can throw (e.g., in headless/CI). This is non-fatal since the user can still copy the URL; catch and continue.

-  // Open the authorization code URL in the browser
-  await open(url.toString());
+  // Open the authorization code URL in the browser
+  try {
+    await open(url.toString());
+  } catch {
+    // Non-fatal: user can still copy the URL from the previous message
+  }

191-197: Annotate return type for clarity

Make the return type explicit to signal that undefined is possible when JWT issuance fails.

-export async function createApiClientWithPublicJWT(
+export async function createApiClientWithPublicJWT(
   auth: LoginResultOk,
   projectRef: string,
   envName: string,
   scopes: string[],
   previewBranch?: string
-) {
+): Promise<ApiClient | undefined> {

206-211: Pass preview branch to ApiClient to preserve context

ApiClient accepts an optional branch parameter; you already take previewBranch in this function but don’t pass it. This can lead to missing preview-branch headers downstream.

-  return new ApiClient(auth.auth.apiUrl, jwt.data.token);
+  return new ApiClient(auth.auth.apiUrl, jwt.data.token, previewBranch);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a510fce and 87414ed.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .cursor/mcp.json (1 hunks)
  • .gitignore (1 hunks)
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (5 hunks)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.orgs.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (2 hunks)
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1 hunks)
  • apps/webapp/app/services/authorization.server.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/install-mcp.sh (1 hunks)
  • packages/cli-v3/package.json (2 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/cli/common.ts (1 hunks)
  • packages/cli-v3/src/cli/index.ts (2 hunks)
  • packages/cli-v3/src/commands/deploy.ts (1 hunks)
  • packages/cli-v3/src/commands/dev.ts (2 hunks)
  • packages/cli-v3/src/commands/init.ts (9 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/login.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/dev/devOutput.ts (2 hunks)
  • packages/cli-v3/src/mcp/auth.ts (1 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/logger.ts (1 hunks)
  • packages/cli-v3/src/mcp/mintlifyClient.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • packages/core/src/v3/apiClient/index.ts (3 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/cli-v3/src/commands/deploy.ts
🚧 Files skipped from review as they are similar to previous changes (33)
  • .cursor/mcp.json
  • packages/cli-v3/src/mcp/capabilities.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • apps/webapp/app/routes/api.v1.runs.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • packages/core/src/v3/apiClient/index.ts
  • packages/cli-v3/src/mcp/mintlifyClient.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • packages/cli-v3/src/mcp/schemas.ts
  • packages/cli-v3/src/commands/install-mcp.ts
  • .gitignore
  • packages/cli-v3/package.json
  • packages/cli-v3/src/apiClient.ts
  • packages/cli-v3/src/mcp/tools.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/cli-v3/src/commands/dev.ts
  • packages/cli-v3/src/dev/devOutput.ts
  • packages/cli-v3/src/commands/mcp.ts
  • packages/cli-v3/src/cli/common.ts
  • packages/cli-v3/src/commands/login.ts
  • packages/cli-v3/src/cli/index.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/mcp/logger.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • packages/cli-v3/src/mcp/context.ts
  • packages/cli-v3/src/commands/init.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • packages/cli-v3/src/utilities/fileSystem.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/utils.ts
  • packages/cli-v3/src/mcp/auth.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Tasks must be exported, even subtasks in the same file.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-08-14T12:13:20.408Z
Learnt from: myftija
PR: triggerdotdev/trigger.dev#2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.408Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
🧬 Code Graph Analysis (5)
packages/cli-v3/src/mcp/auth.ts (9)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (12-24)
packages/cli-v3/src/utilities/session.ts (2)
  • LoginResult (19-28)
  • LoginResultOk (7-17)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/utilities/isPersonalAccessToken.ts (1)
  • NotPersonalAccessTokenError (7-12)
packages/cli-v3/src/apiClient.ts (3)
  • CliApiClient (55-763)
  • createAuthorizationCode (69-77)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readAuthConfigProfile (92-102)
  • writeAuthConfigProfile (81-90)
packages/cli-v3/src/commands/login.ts (1)
  • getPersonalAccessToken (349-378)
packages/core/src/v3/apiClientManager/index.ts (1)
  • client (57-63)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (3)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/presenters/v3/DevPresence.server.ts (1)
  • devPresence (29-36)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (6)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (2)
  • loader (17-71)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/v3/models/workerDeployment.server.ts (1)
  • findCurrentWorkerFromEnvironment (198-224)
apps/webapp/app/db.server.ts (1)
  • $replica (102-105)
packages/core/src/v3/schemas/api.ts (2)
  • GetWorkerByTagResponse (90-99)
  • GetWorkerByTagResponse (101-101)
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (5)
apps/webapp/app/routes/api.v1.orgs.ts (1)
  • loader (7-37)
apps/webapp/app/routes/api.v1.projects.ts (1)
  • loader (8-54)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/schemas/api.ts (6)
  • GetProjectsResponseBody (50-50)
  • GetProjectsResponseBody (52-52)
  • CreateProjectRequestBody (65-67)
  • CreateProjectRequestBody (69-69)
  • GetProjectResponseBody (30-46)
  • GetProjectResponseBody (48-48)
packages/cli-v3/src/apiClient.ts (1)
  • createProject (158-168)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

433-435: LGTM: Genericizing action routes and plumbing TResource/TBodySchema through the public API

The expanded generics and typed propagation into both the options and handler types for action routes look solid and consistent with the loader path.

Also applies to: 481-483, 503-516

packages/cli-v3/src/mcp/utils.ts (1)

3-13: LGTM on the CallToolResult shape

Returning isError: true with a single text content item is consistent with the MCP tool result expectations.

packages/cli-v3/src/mcp/auth.ts (3)

30-62: Env-based PAT path looks solid

Validation via isPersonalAccessToken, resilient API URL fallback, and whoAmI verification are all good.


64-88: Config-based token path is correct

Reuses stored apiUrl/token and verifies identity; sensible defaults applied. No issues spotted.


165-189: Login permission elicitation is well-gated

Capability check and the schema for allowLogin look good; returning a boolean is straightforward.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (20)
packages/cli-v3/src/mcp/capabilities.ts (1)

3-11: Reduce duplication with a generic capability checker

All three functions share the same pattern. A small helper keeps it DRY and easier to extend if new capabilities arrive.

Apply this diff:

 export function hasRootsCapability(server: McpServer) {
-  const capabilities = server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "roots" in capabilities && typeof capabilities.roots === "object";
+  return hasCapability(server, "roots");
 }
 
 export function hasSamplingCapability(server: McpServer) {
-  const capabilities = server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "sampling" in capabilities && typeof capabilities.sampling === "object";
+  return hasCapability(server, "sampling");
 }
 
 export function hasElicitationCapability(server: McpServer) {
-  const capabilities = server.server.getClientCapabilities();
-
-  if (!capabilities) {
-    return false;
-  }
-
-  return "elicitation" in capabilities && typeof capabilities.elicitation === "object";
+  return hasCapability(server, "elicitation");
 }
+
+function hasCapability(
+  server: McpServer,
+  key: "roots" | "sampling" | "elicitation"
+) {
+  const capabilities = server.server.getClientCapabilities();
+  if (!capabilities) return false;
+  return key in capabilities && typeof (capabilities as any)[key] === "object";
+}

Also applies to: 13-21, 23-31

packages/cli-v3/src/commands/update.ts (2)

37-41: Use the correct command name in wrapCommandAction

This action is for the update command but is labeled "dev", which can confuse telemetry/logging.

-      wrapCommandAction("dev", UpdateCommandOptions, options, async (opts) => {
+      wrapCommandAction("update", UpdateCommandOptions, options, async (opts) => {

331-339: Harden resolution when basedir is omitted

resolve.sync expects a basedir; leaving it undefined will throw and rely on catch. Provide a safe default to improve resilience for external callers (this function is exported).

 export async function tryResolveTriggerPackageVersion(
   name: string,
-  basedir?: string
+  basedir?: string
 ): Promise<string | undefined> {
   try {
-    const resolvedPath = nodeResolve.sync(name, {
-      basedir,
-    });
+    const resolveBase = basedir ?? process.cwd();
+    const resolvedPath = nodeResolve.sync(name, { basedir: resolveBase });
packages/cli-v3/src/mcp/schemas.ts (2)

8-14: Validate the project ref format

The description says it starts with proj_, but it’s not enforced. Add a simple refinement to catch mistakes early.

 export const ProjectRefSchema = z
   .string()
+  .refine((v) => v.startsWith("proj_"), {
+    message: "Project ref must start with 'proj_'",
+  })
   .describe(
     "The trigger.dev project ref, starts with proj_. We will attempt to automatically detect the project ref if running inside a directory that includes a trigger.config.ts file, or if you pass the --project-ref option to the MCP server."
   )
   .optional();

146-168: Tighten pagination and ID validation

  • Enforce a sensible range for limit (matches the description “Up to 100”).
  • Validate cursors that “start with run_” per the description.
 export const ListRunsInput = CommonProjectsInput.extend({
-  cursor: z.string().describe("The cursor to use for pagination, starts with run_").optional(),
+  cursor: z
+    .string()
+    .refine((v) => v.startsWith("run_"), { message: "Cursor must start with 'run_'" })
+    .describe("The cursor to use for pagination, starts with run_")
+    .optional(),
   limit: z
     .number()
     .int()
-    .describe("The number of runs to list in a single page. Up to 100")
+    .min(1)
+    .max(100)
+    .describe("The number of runs to list in a single page. Up to 100")
     .optional(),
packages/cli-v3/src/mcp/tools/docs.ts (3)

1-1: Align Zod import style with the rest of the codebase

Other modules import Zod as a named import. Keep it consistent.

-import z from "zod";
+import { z } from "zod";

3-3: Return structured errors from the handler

Consider importing respondWithError to translate unexpected failures into MCP-compliant error responses.

-import { toolHandler } from "../utils.js";
+import { respondWithError, toolHandler } from "../utils.js";

10-14: Add input validation, normalize input, and robust error handling for the docs search tool

To improve resilience and clarity when calling performSearch, consider:

  • Enforcing a non‐empty, trimmed query in your Zod schema.
  • Wrapping the handler in a try/catch to emit a clear error response on failure.
  • Safely handling both results.text and results.result (or falling back to a default message) before rendering.

Suggested diff for packages/cli-v3/src/mcp/tools/docs.ts (also apply at lines 16–21):

   inputSchema: {
-    query: z.string(),
+    query: z.string().min(1, "Query cannot be empty"),
   },
-  handler: toolHandler({ query: z.string() }, async (input, { ctx }) => {
-    ctx.logger?.log("calling search_docs", { input });
-
-    const results = await performSearch(input.query);
-
-    return {
-      content: [{ type: "text", text: results.result }],
-    };
-  }),
+  handler: toolHandler({ query: z.string().min(1) }, async (input, { ctx }) => {
+    ctx.logger?.log("calling search_docs", { input });
+    try {
+      const results = await performSearch(input.query.trim());
+      const text =
+        results.text ?? results.result ?? "No results returned from search.";
+      return { content: [{ type: "text", text: String(text) }] };
+    } catch (err) {
+      return respondWithError(
+        err instanceof Error ? err.message : "Failed to search docs"
+      );
+    }
+  }),

Please verify the actual shape returned by performSearch (via parseResponse) to confirm which property—text, result, or otherwise—should be used.

packages/cli-v3/src/mcp/utils.ts (1)

18-38: Include Zod validation issues in error responses for better feedback.

Right now only name/message are surfaced, which hides Zod field-level errors. Returning issues makes tool failures actionable.

Apply this diff to enumerate Zod issues:

 function enumerateError(error: unknown) {
   if (!error) {
     return error;
   }
 
   if (typeof error !== "object") {
     return error;
   }
 
+  // Surface Zod validation issues verbosely for callers
+  if (error instanceof z.ZodError) {
+    return {
+      name: "ZodError",
+      message: error.message,
+      issues: error.issues,
+    };
+  }
+
   const newError: Record<string, unknown> = {};
 
   const errorProps = ["name", "message"] as const;
 
   for (const prop of errorProps) {
     if (prop in error) {
       newError[prop] = (error as Record<string, unknown>)[prop];
     }
   }
 
   return newError;
 }
packages/cli-v3/src/mcp/tools/tasks.ts (2)

14-24: Require branch when environment is preview to prevent ambiguous calls.

Downstream APIs typically require a branch for preview environments. Fail fast with a clear error rather than surfacing a later API error.

Apply this diff:

     if (ctx.options.devOnly && input.environment !== "dev") {
       return respondWithError(
         `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
       );
     }
 
+    if (input.environment === "preview" && !input.branch) {
+      return respondWithError(
+        `A "branch" is required when using the preview environment. Please provide the branch name.`
+      );
+    }
+
     const projectRef = await ctx.getProjectRef({
       projectRef: input.projectRef,
       cwd: input.configPath,
     });

47-57: Require branch when environment is preview for trigger_task as well.

Same rationale as get_tasks: avoid ambiguous preview triggers without a branch.

Apply this diff:

     if (ctx.options.devOnly && input.environment !== "dev") {
       return respondWithError(
         `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
       );
     }
 
+    if (input.environment === "preview" && !input.branch) {
+      return respondWithError(
+        `A "branch" is required when using the preview environment. Please provide the branch name.`
+      );
+    }
+
     const projectRef = await ctx.getProjectRef({
       projectRef: input.projectRef,
       cwd: input.configPath,
     });
packages/cli-v3/src/mcp/tools/runs.ts (1)

132-146: Validate from/to timestamps to avoid passing Invalid Date to the API.

new Date("bad") yields Invalid Date. Validate and fail early with a clear message.

Apply this diff:

     const $from = typeof input.from === "string" ? new Date(input.from) : undefined;
     const $to = typeof input.to === "string" ? new Date(input.to) : undefined;
 
+    if ($from && Number.isNaN($from.getTime())) {
+      return respondWithError(`Invalid "from" timestamp. Expected an ISO 8601 string.`);
+    }
+
+    if ($to && Number.isNaN($to.getTime())) {
+      return respondWithError(`Invalid "to" timestamp. Expected an ISO 8601 string.`);
+    }
+
     const result = await apiClient.listRuns({
       after: input.cursor,
       limit: input.limit,
       status: input.status,
       taskIdentifier: input.taskIdentifier,
       version: input.version,
       tag: input.tag,
       from: $from,
       to: $to,
       period: input.period,
       machine: input.machine,
     });
packages/cli-v3/src/mcp/tools/deploys.ts (3)

63-71: Preserve the current environment when spawning the CLI.

Overwriting env with only TRIGGER_MCP_SERVER can drop required variables (PATH, proxies, auth). Merge with process.env.

Apply this diff:

     const deployProcess = x(nodePath, [cliPath, ...args], {
       nodeOptions: {
         cwd: cwd.cwd,
         env: {
-          TRIGGER_MCP_SERVER: "1",
+          ...process.env,
+          TRIGGER_MCP_SERVER: "1",
         },
       },
     });

72-76: Avoid implicit any in logs array.

Initialize with an explicit type.

Apply this diff:

-    const logs = [];
+    const logs: string[] = [];

9-11: Use fileURLToPath for robust file:// URI conversion (Windows-safe).

Simple string replace can yield incorrect paths on Windows or edge cases. Use Node’s fileURLToPath.

Apply this diff:

 import { VERSION } from "../../version.js";
 import { resolveSync as esmResolve } from "mlly";
+import { fileURLToPath } from "node:url";
 
@@
-function fileUriToPath(uri: string) {
-  return uri.replace("file://", "");
-}
+function fileUriToPath(uri: string) {
+  return fileURLToPath(uri);
+}

Also applies to: 202-204

packages/cli-v3/src/mcp/context.ts (2)

170-173: Build dashboard URLs with URL() to avoid missing/double slashes

String concatenation can lead to // or missing /; using URL is safer.

Apply this diff:

   public async getDashboardUrl(path: string) {
     const auth = await this.getAuth();
-    return `${auth.dashboardUrl}${path}`;
+    return new URL(path, auth.dashboardUrl).toString();
   }

188-190: Use fileURLToPath for robust file:// URI conversion (Windows-safe)

Simple replace won’t handle triple slashes, percent-encoding, or Windows drive letters. Use node:url’s fileURLToPath.

Apply this diff:

-function fileUriToPath(uri: string) {
-  return uri.replace("file://", "");
-}
+function fileUriToPath(uri: string) {
+  try {
+    return fileURLToPath(uri);
+  } catch {
+    // Fallback for unexpected inputs
+    return uri.replace(/^file:\/\//, "");
+  }
+}

And add this import at the top of the file:

import { fileURLToPath } from "node:url";
packages/cli-v3/src/mcp/tools/orgs.ts (3)

215-218: Harden manual setup fetch and replace all occurrences

  • Check response.ok to fail fast on HTTP errors.
  • Replace all occurrences of placeholders to avoid partial replacement.

Apply this diff:

-  const response = await fetch("https://trigger.dev/docs/manual-setup.md");
-  let text = await response.text();
+  const response = await fetch("https://trigger.dev/docs/manual-setup.md");
+  if (!response.ok) {
+    throw new Error(`Failed to fetch manual setup guide (${response.status} ${response.statusText})`);
+  }
+  let text = await response.text();
 
-  text = text.replace("<your-project-ref>", projectRef);
+  text = text.replaceAll("<your-project-ref>", projectRef);
 
-  text = text.replace("tr_dev_xxxxxxxxxx", apiKey ?? "tr_dev_xxxxxxxxxx");
-  text = text.replace(
+  text = text.replaceAll("tr_dev_xxxxxxxxxx", apiKey ?? "tr_dev_xxxxxxxxxx");
+  text = text.replaceAll(
     "https://your-trigger-instance.com",
     apiUrl ?? "https://your-trigger-instance.com"
   );

Also applies to: 219-225


15-16: Use toolHandler for no-input tools for consistent validation/logging (list_orgs)

Aligns with other tools and standardizes input parsing/error handling.

Apply this diff:

-  inputSchema: {},
-  handler: async (input: unknown, { ctx }: ToolMeta): Promise<CallToolResult> => {
+  inputSchema: {},
+  handler: toolHandler({}, async (_input, { ctx }) => {
@@
-  },
+  }),

Also applies to: 35-35


43-44: Use toolHandler for no-input tools for consistent validation/logging (list_projects)

Same consistency rationale as above.

Apply this diff:

-  inputSchema: {},
-  handler: async (input: unknown, { ctx }: ToolMeta): Promise<CallToolResult> => {
+  inputSchema: {},
+  handler: toolHandler({}, async (_input, { ctx }) => {
@@
-  },
+  }),

Also applies to: 103-103

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 87414ed and ada9a72.

📒 Files selected for processing (16)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/config.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/deploys.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/docs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/orgs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/previewBranches.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/runs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/tasks.ts (1 hunks)
  • packages/cli-v3/src/mcp/types.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/cli-v3/src/mcp/config.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/cli-v3/src/mcp/tools.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • packages/cli-v3/src/commands/mcp.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/capabilities.ts
  • packages/cli-v3/src/mcp/types.ts
  • packages/cli-v3/src/mcp/tools/previewBranches.ts
  • packages/cli-v3/src/mcp/tools/runs.ts
  • packages/cli-v3/src/mcp/tools/docs.ts
  • packages/cli-v3/src/mcp/tools/tasks.ts
  • packages/cli-v3/src/mcp/tools/deploys.ts
  • packages/cli-v3/src/mcp/utils.ts
  • packages/cli-v3/src/mcp/context.ts
  • packages/cli-v3/src/mcp/tools/orgs.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/mcp/schemas.ts
🧠 Learnings (9)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : ALWAYS generate Trigger.dev tasks using the `task` function from `trigger.dev/sdk/v3` and export them as shown in the correct pattern.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
  • packages/cli-v3/src/commands/update.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When using metadata in tasks, use the `metadata` API as shown, and only inside run functions or task lifecycle hooks.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task from backend code, use `tasks.trigger`, `tasks.batchTrigger`, or `tasks.triggerAndPoll` as shown in the examples.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Tasks must be exported, even subtasks in the same file.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When implementing scheduled (cron) tasks, use `schedules.task` from `trigger.dev/sdk/v3` and follow the shown patterns.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When implementing schema tasks, use `schemaTask` from `trigger.dev/sdk/v3` and validate payloads as shown.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
  • packages/cli-v3/src/mcp/schemas.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Before generating any code for Trigger.dev tasks, verify: (1) Are you importing from `trigger.dev/sdk/v3`? (2) Have you exported every task? (3) Have you generated any deprecated code patterns?

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : You MUST `export` every task, including subtasks, in Trigger.dev task files.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : The `run` function contains your task logic in Trigger.dev tasks.

Applied to files:

  • packages/cli-v3/src/mcp/tools/tasks.ts
🧬 Code Graph Analysis (11)
packages/cli-v3/src/mcp/capabilities.ts (1)
packages/cli-v3/src/mcp/context.ts (3)
  • hasRootsCapability (175-177)
  • hasSamplingCapability (179-181)
  • hasElicitationCapability (183-185)
packages/cli-v3/src/mcp/types.ts (1)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (23-186)
packages/cli-v3/src/mcp/tools/previewBranches.ts (4)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/schemas.ts (2)
  • ListPreviewBranchesInput (203-211)
  • ListPreviewBranchesInput (213-213)
packages/cli-v3/src/mcp/utils.ts (2)
  • toolHandler (40-53)
  • respondWithError (6-16)
packages/core/src/v3/taskContext/index.ts (1)
  • ctx (26-28)
packages/cli-v3/src/mcp/tools/runs.ts (3)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/schemas.ts (6)
  • GetRunDetailsInput (135-142)
  • GetRunDetailsInput (144-144)
  • CommonRunsInput (129-131)
  • CommonRunsInput (133-133)
  • ListRunsInput (146-167)
  • ListRunsInput (169-169)
packages/cli-v3/src/mcp/utils.ts (2)
  • toolHandler (40-53)
  • respondWithError (6-16)
packages/cli-v3/src/mcp/tools/docs.ts (4)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/utils.ts (1)
  • toolHandler (40-53)
packages/core/src/v3/taskContext/index.ts (1)
  • ctx (26-28)
packages/cli-v3/src/mcp/mintlifyClient.ts (1)
  • performSearch (1-16)
packages/cli-v3/src/mcp/tools/tasks.ts (3)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/schemas.ts (4)
  • CommonProjectsInput (43-59)
  • CommonProjectsInput (61-61)
  • TriggerTaskInput (63-125)
  • TriggerTaskInput (127-127)
packages/cli-v3/src/mcp/utils.ts (2)
  • toolHandler (40-53)
  • respondWithError (6-16)
packages/cli-v3/src/mcp/tools/deploys.ts (6)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/schemas.ts (4)
  • DeployInput (182-195)
  • DeployInput (197-197)
  • ListDeploysInput (199-199)
  • ListDeploysInput (201-201)
packages/cli-v3/src/mcp/utils.ts (2)
  • toolHandler (40-53)
  • respondWithError (6-16)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (23-186)
packages/cli-v3/src/commands/update.ts (2)
  • tryResolveTriggerPackageVersion (331-367)
  • getPackageJson (413-420)
packages/core/src/v3/index.ts (1)
  • VERSION (85-85)
packages/cli-v3/src/mcp/utils.ts (1)
packages/cli-v3/src/mcp/types.ts (1)
  • ToolMeta (5-7)
packages/cli-v3/src/mcp/context.ts (6)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
packages/cli-v3/src/mcp/auth.ts (1)
  • mcpAuth (24-153)
packages/cli-v3/src/apiClient.ts (1)
  • CliApiClient (55-763)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/mcp/capabilities.ts (3)
  • hasRootsCapability (3-11)
  • hasSamplingCapability (13-21)
  • hasElicitationCapability (23-31)
packages/cli-v3/src/mcp/tools/orgs.ts (4)
packages/cli-v3/src/mcp/types.ts (1)
  • ToolMeta (5-7)
packages/cli-v3/src/mcp/utils.ts (2)
  • respondWithError (6-16)
  • toolHandler (40-53)
packages/cli-v3/src/mcp/schemas.ts (4)
  • CreateProjectInOrgInput (15-22)
  • CreateProjectInOrgInput (24-24)
  • InitializeProjectInput (26-39)
  • InitializeProjectInput (41-41)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/mcp/schemas.ts (1)
packages/core/src/v3/schemas/api.ts (3)
  • RunStatus (703-730)
  • RunStatus (732-732)
  • ApiDeploymentListParams (1163-1167)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
packages/cli-v3/src/commands/update.ts (2)

322-325: Good: resolve package version relative to the project’s package.json

Passing dirname(packageJsonPath) as basedir makes version resolution deterministic in monorepos and non-root setups.


342-356: Approach looks solid for avoiding nested package.json in dist folders

The test filter prevents accidentally reading sub-package manifests under dist/commonjs or dist/esm, ensuring the root package.json is used for version lookup.

packages/cli-v3/src/mcp/schemas.ts (1)

63-125: Nice payload parsing with actionable error

Transforming a JSON string payload with a precise validation error is a good DX touch.

packages/cli-v3/src/mcp/types.ts (1)

5-7: LGTM: ToolMeta composition is clear and useful

Typing ToolMeta with RequestHandlerExtra plus McpContext enables strongly-typed ctx in tool handlers.

packages/cli-v3/src/mcp/tools/deploys.ts (2)

126-153: Confirm the resolved CLI path points to the executable bin, not the module entry.

esmResolve("trigger.dev") likely returns the package entry module, not the CLI bin. Running node may not behave like the CLI. Consider resolving the bin from packageJson.bin (string or map) and executing that path.

Would you like me to generate a patch that derives the bin path from packageJson.bin (handling both string and object forms) and prefers that over the module entry? This reduces the chance of invoking the wrong file.


118-122: Pass only list parameters to listDeployments.

Ensure apiClient.listDeployments expects the filtering/pagination fields, not project/environment/branch (those are already baked into apiClient). If it rejects extra fields, derive a param object instead of passing input wholesale.

If needed, I can propose a small refactor to pick only the allowed keys (e.g., cursor/limit/status/tag/from/to/period) before calling listDeployments.

packages/cli-v3/src/mcp/tools/orgs.ts (1)

136-174: InitializeProjectInput.projectRef is correctly optional

ProjectRefSchema is defined with .optional(), so InitializeProjectInput.projectRef is optional as intended and the handler’s branch for missing projectRef will work. No schema changes required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

577-583: Apply CORS wrapper to the 413 response

This path returns a bare json Response, skipping wrapResponse; cross-origin clients won’t see expected CORS headers.

-      if (maxContentLength) {
+      if (maxContentLength) {
         const contentLength = request.headers.get("content-length");
 
-        if (!contentLength or parseInt(contentLength) > maxContentLength) {
-          return json({ error: "Request body too large" }, { status: 413 });
-        }
+        if (!contentLength || parseInt(contentLength) > maxContentLength) {
+          return await wrapResponse(
+            request,
+            json({ error: "Request body too large" }, { status: 413 }),
+            corsStrategy !== "none"
+          );
+        }
       }
♻️ Duplicate comments (16)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (6)

1-1: LGTM: Remix server import standardized to @remix-run/node.

Matches our convention for server-only routes.


15-17: Add no-store and Vary: Authorization to 401 to prevent caching/auth leakage.

401 responses can be cached by intermediaries if not marked. Vary ensures different auth states don’t share a cached response.

Apply this diff:

-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store", Vary: "Authorization" } }
+    );

Optional: also include WWW-Authenticate: Bearer for RFC compliance.


21-23: Mark 400 responses as non-cacheable.

These should not be cached by clients/CDNs.

Apply this diff:

-    return json({ error: "Invalid Params" }, { status: 400 });
+    return json(
+      { error: "Invalid Params" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );

40-42: Don’t cache authorization-dependent 404.

This 404 depends on user membership; ensure intermediaries don’t cache it across users.

Apply this diff:

-    return json({ error: "Project not found" }, { status: 404 });
+    return json(
+      { error: "Project not found" },
+      { status: 404, headers: { "Cache-Control": "no-store", Vary: "Authorization" } }
+    );

50-52: Don’t cache user-scoped environment lookup failures.

This result is user-specific; add cache-busting headers.

Apply this diff:

-    return json({ error: envResult.error }, { status: 404 });
+    return json(
+      { error: envResult.error },
+      { status: 404, headers: { "Cache-Control": "no-store", Vary: "Authorization" } }
+    );

56-59: Harden devPresence lookup and make responses non-cacheable.

devPresence.isConnected can throw (e.g., Redis issues). Wrap in try/catch; return 503 on failure. Also, this status is volatile and auth-scoped—add no-store and Vary headers.

Apply this diff:

-  const isConnected = await devPresence.isConnected(runtimeEnv.id);
-
-  return json({ isConnected });
+  try {
+    const isConnected = await devPresence.isConnected(runtimeEnv.id);
+    return json(
+      { isConnected },
+      { headers: { "Cache-Control": "no-store", Vary: "Authorization" } }
+    );
+  } catch {
+    return json(
+      { error: "Dev presence service unavailable" },
+      { status: 503, headers: { "Cache-Control": "no-store", Vary: "Authorization" } }
+    );
+  }
packages/cli-v3/src/mcp/context.ts (1)

58-79: Propagate branch to ApiClient so x-trigger-branch header is set

You accept a branch and pass it to CliApiClient, but don’t pass it to the core ApiClient. This means downstream requests won’t carry the preview branch header.

Apply this diff:

-    return new ApiClient(cliApiClient.apiURL, jwt.data.token);
+    return new ApiClient(cliApiClient.apiURL, jwt.data.token, options.branch);

Run this to find other ApiClient constructions that may also need the branch propagated:

#!/bin/bash
# Find "new ApiClient(" occurrences and show surrounding context
rg -nP --type=ts -C2 '\bnew\s+ApiClient\s*\('
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1)

66-73: Guard against invalid JSON bodies to return 400 instead of 500

request.json() can throw for malformed JSON. Wrap it and feed the parsed value into Zod, so clients get a 400 with a clear error.

-  const parsedBody = RequestBodySchema.safeParse(await request.json());
+  let body: unknown;
+  try {
+    body = await request.json();
+  } catch {
+    return json({ error: "Invalid JSON in request body" }, { status: 400 });
+  }
+
+  const parsedBody = RequestBodySchema.safeParse(body);
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)

41-41: Fix version filter casing: "V3" → "v3"

Newly created projects use version "v3", but the loader filters on "V3", causing freshly created projects to be omitted.

-      version: "V3",
+      version: "v3",
packages/core/src/v3/schemas/api.ts (7)

32-36: Enforce externalRef format in schema (starts with proj_)

The description states the contract but the schema doesn’t enforce it. Add a regex so consumers get a clear validation error.

 export const GetProjectResponseBody = z.object({
   id: z.string(),
-  externalRef: z
-    .string()
-    .describe(
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9]+$/, "Must start with proj_")
+    .describe(
       "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
     ),

84-93: Export the GetWorkerTaskResponse type for consumers

The schema is exported, but the TS type isn’t. Exporting the inferred type improves DX where nested types are referenced.

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

95-103: Constrain worker.engine to RunEngineVersion

Avoid free-form strings for engine; use the shared union for consistency.

 export const GetWorkerByTagResponse = z.object({
   worker: z.object({
     id: z.string(),
     version: z.string(),
-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),
     sdkVersion: z.string().nullish(),
     cliVersion: z.string().nullish(),
     tasks: z.array(GetWorkerTaskResponse),
   }),
 });

1162-1168: Apply integer and default bounds to deployment list limit

Per the description, enforce int, min/max, and set default 20. Remove optional to allow defaulting when omitted.

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .min(1, "Limit must be at least 1")
-  .max(100, "Limit must be less than 100")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1, "Limit must be at least 1")
+  .max(100, "Limit must be less than 100")
+  .default(20)
+  .describe("The number of deployments to return, defaults to 20 (max 100)");

1169-1176: Nit: clarify naming between plain-shape and schema

ApiDeploymentListParams is a plain object passed to z.object(); consider renaming to a Shape to reduce confusion. Keep a back-compat alias if this is already public.

-export const ApiDeploymentListParams = {
+export const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);
+// Optional: back-compat alias if external consumers import the shape
+export const ApiDeploymentListParams = ApiDeploymentListParamsShape;

Also applies to: 1175-1176


1187-1206: Use GitMeta for deployment git metadata

Unify with the rest of the codebase and gain stronger typing by reusing GitMeta.

 export const ApiDeploymentListResponseItem = z.object({
   id: z.string(),
   createdAt: z.coerce.date(),
   shortCode: z.string(),
   version: z.string(),
   runtime: z.string(),
   runtimeVersion: z.string(),
   status: z.enum([
     "PENDING",
     "BUILDING",
     "DEPLOYING",
     "DEPLOYED",
     "FAILED",
     "CANCELED",
     "TIMED_OUT",
   ]),
   deployedAt: z.coerce.date().optional(),
-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),
   error: DeploymentErrorData.optional(),
 });

1211-1221: Use GitMeta for branch git metadata

Same rationale as deployments: stronger typing and consistency.

 export const ApiBranchListResponseBody = z.object({
   branches: z.array(
     z.object({
       id: z.string(),
       name: z.string(),
       createdAt: z.coerce.date(),
       updatedAt: z.coerce.date(),
-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),
       isPaused: z.boolean(),
     })
   ),
 });
🧹 Nitpick comments (15)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (3)

6-6: Avoid importing from another route file; extract shared logic into a service module.

Importing getEnvironmentFromEnv from a sibling route couples routes and can create circular deps. Prefer moving it to a shared server module (e.g., a service/utility under app/) and importing from there.


8-10: Nit: Enforce non-empty projectRef in ParamsSchema.

Prevents accidental empty/undefined strings from passing validation.

Apply this diff:

-const ParamsSchema = z.object({
-  projectRef: z.string(),
-});
+const ParamsSchema = z.object({
+  projectRef: z.string().min(1, "projectRef is required"),
+});

27-38: Return only needed fields from Prisma (select: { id: true }).

Reduces payload and avoids accidental exposure if this block changes later.

Apply this diff:

-  const project = await prisma.project.findFirst({
-    where: {
-      externalRef: projectRef,
-      organization: {
-        members: {
-          some: {
-            userId: authenticationResult.userId,
-          },
-        },
-      },
-    },
-  });
+  const project = await prisma.project.findFirst({
+    where: {
+      externalRef: projectRef,
+      organization: {
+        members: {
+          some: {
+            userId: authenticationResult.userId,
+          },
+        },
+      },
+    },
+    select: { id: true },
+  });
packages/cli-v3/src/mcp/context.ts (4)

119-126: Support file:// URIs passed as cwd

If a caller provides cwd as a file:// URI, extname/dirname will misbehave. Normalize file URIs to filesystem paths first.

Apply this diff:

-    // If cwd is a path to the actual trigger.config.ts file, then we should set the cwd to the directory of the file
-    let $cwd = cwd ? (path.extname(cwd) !== "" ? path.dirname(cwd) : cwd) : undefined;
+    // If cwd is a path to the actual trigger.config.ts file, then we should set the cwd to the directory of the file
+    const normalizedCwd = cwd?.startsWith("file://") ? fileURLToPath(cwd) : cwd;
+    let $cwd = normalizedCwd
+      ? path.extname(normalizedCwd) !== ""
+        ? path.dirname(normalizedCwd)
+        : normalizedCwd
+      : undefined;

171-174: Build dashboard URL robustly to avoid double/missing slashes

Concatenation can yield malformed URLs depending on trailing/leading slashes. Use URL for safe joining.

Apply this diff:

-    return `${auth.dashboardUrl}${path}`;
+    return new URL(path, auth.dashboardUrl).toString();

81-93: Harden listRoots call against transport/protocol errors

If listRoots throws (e.g., server unavailable), this method will throw and break callers relying on undefined fallback. Consider catching and returning undefined.

Apply this diff:

-    const response = await this.server.server.listRoots();
+    let response;
+    try {
+      response = await this.server.server.listRoots();
+    } catch {
+      return undefined;
+    }

106-116: Project ref detection may be too strict when configFile is absent

Requiring config.configFile to be truthy can skip valid in-memory/overrides configs. If config.project is present and well-formed, we can use it regardless of configFile presence.

If acceptable, simplify the condition:

-    if (
-      config?.configFile &&
-      typeof config.project === "string" &&
-      config.project.startsWith("proj_")
-    ) {
+    if (typeof config?.project === "string" && config.project.startsWith("proj_")) {
       return config.project;
     }

Please confirm whether loadConfig can yield a valid project via overrides without setting configFile; if so, the simplified check is preferable.

packages/core/src/v3/isomorphic/dates.ts (5)

13-35: Trim inputs and use Number.isNaN for safer parsing

Trimming avoids surprising failures on inputs like " 1629302400 ". Number.isNaN is preferred over global isNaN.

Apply this diff:

-export function parseDate(input: string): Date | undefined {
-  if (typeof input !== "string") return undefined;
-
-  // Handle pure numeric strings as epoch values
-  if (/^\d+$/.test(input)) {
-    const num = Number(input);
-
-    if (input.length === 10) {
-      // Epoch seconds
-      return new Date(num * 1000);
-    } else if (input.length === 13) {
-      // Epoch milliseconds
-      return new Date(num);
-    } else {
-      // Unsupported numeric length
-      return undefined;
-    }
-  }
-
-  // Handle general date strings
-  const date = new Date(input);
-  return isNaN(date.getTime()) ? undefined : date;
-}
+export function parseDate(input: string): Date | undefined {
+  if (typeof input !== "string") return undefined;
+
+  const s = input.trim();
+
+  // Handle pure numeric strings as epoch values
+  if (/^\d+$/.test(s)) {
+    const num = Number(s);
+
+    if (s.length === 10) {
+      // Epoch seconds
+      return new Date(num * 1000);
+    } else if (s.length === 13) {
+      // Epoch milliseconds
+      return new Date(num);
+    } else {
+      // Unsupported numeric length
+      return undefined;
+    }
+  }
+
+  // Handle general date strings
+  const date = new Date(s);
+  return Number.isNaN(date.getTime()) ? undefined : date;
+}

14-14: Nit: Redundant typeof guard with TS signature

Given input: string, the typeof check is redundant at call sites in TS. If you need to harden for JS callers, keep it; otherwise you can drop it to reduce a branch.


5-9: Doc update: note that leading/trailing whitespace is ignored

Keep the docs aligned with the implementation to prevent confusion.

  * - Epoch milliseconds (13-digit numeric string, e.g. "1629302400000")
+ * - Leading/trailing whitespace is ignored

16-30: Optional: Consider supporting signed epoch values and relaxed ms/seconds detection

If you expect dates before 1970 (negative epoch) or very early post-1970 milliseconds (11–12 digits), the current 10/13-digit rule will reject valid inputs. A common heuristic is:

  • If abs(digits) <= 10 => seconds
  • Else => milliseconds

If you want this, I can provide a targeted patch and tests.


1-12: Add unit tests for edge cases

Recommend adding tests covering:

  • "1629302400" (seconds), "1629302400000" (ms)
  • " 1629302400 " (trimmed)
  • ISO strings with/without timezone ("2025-08-18", "2025-08-18T12:34:56Z")
  • Invalid numeric lengths ("123", "123456789012")
  • Invalid strings ("not a date")
  • Optional (if enabled): negative seconds/ms

I can draft a dates.test.ts with these cases.

apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)

49-51: Unreachable 404; findMany never returns null

prisma.project.findMany returns an array (possibly empty). The current check will never trigger. Decide on semantics:

  • If empty list is acceptable, remove the 404 branch.
  • If you want 404 on empty, check length.

Option A (return 404 on empty):

-  if (!projects) {
-    return json({ error: "Projects not found" }, { status: 404 });
-  }
+  if (projects.length === 0) {
+    return json({ error: "Projects not found" }, { status: 404 });
+  }

Option B (prefer 200 with []):

-  if (!projects) {
-    return json({ error: "Projects not found" }, { status: 404 });
-  }
+  // Intentionally return 200 with an empty array when there are no projects
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)

429-475: Optional: add shouldRetryNotFound to action builder for parity with loader

Loader emits x-should-retry on 404 via shouldRetryNotFound. Consider mirroring this in the action builder options so clients can implement uniform retry semantics.

 type ApiKeyActionRouteBuilderOptions<
   TParamsSchema extends AnyZodSchema | undefined = undefined,
   TSearchParamsSchema extends AnyZodSchema | undefined = undefined,
   THeadersSchema extends AnyZodSchema | undefined = undefined,
   TBodySchema extends AnyZodSchema | undefined = undefined,
   TResource = never
 > = {
   params?: TParamsSchema;
   searchParams?: TSearchParamsSchema;
   headers?: THeadersSchema;
   allowJWT?: boolean;
   corsStrategy?: "all" | "none";
   method?: "POST" | "PUT" | "DELETE" | "PATCH";
+  shouldRetryNotFound?: boolean;
   findResource?: (
     params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TParamsSchema>
       : undefined,
     authentication: ApiAuthenticationResultSuccess,
     searchParams: TSearchParamsSchema extends
       | z.ZodFirstPartySchemaTypes
       | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TSearchParamsSchema>
       : undefined
   ) => Promise<TResource | undefined>;

And when returning 404:

-      if (options.findResource && !resource) {
+      if (options.findResource && !resource) {
         return await wrapResponse(
           request,
-          json({ error: "Resource not found" }, { status: 404 }),
+          json(
+            { error: "Resource not found" },
+            { status: 404, headers: { "x-should-retry": options.shouldRetryNotFound ? "true" : "false" } }
+          ),
           corsStrategy !== "none"
         );
       }

477-523: Optional: pass apiVersion to action handlers for consistency

Loader and PAT loader provide apiVersion; action handlers don’t. Plumb apiVersion through the action to give handlers consistent context.

Type addition:

 type ApiKeyActionHandlerFunction<
   TParamsSchema extends AnyZodSchema | undefined,
   TSearchParamsSchema extends AnyZodSchema | undefined,
   THeadersSchema extends AnyZodSchema | undefined = undefined,
   TBodySchema extends AnyZodSchema | undefined = undefined,
   TResource = never
 > = (args: {
   params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
     ? z.infer<TParamsSchema>
     : undefined;
   searchParams: TSearchParamsSchema extends
     | z.ZodFirstPartySchemaTypes
     | z.ZodDiscriminatedUnion<any, any>
     ? z.infer<TSearchParamsSchema>
     : undefined;
   headers: THeadersSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
     ? z.infer<THeadersSchema>
     : undefined;
   body: TBodySchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
     ? z.infer<TBodySchema>
     : undefined;
   authentication: ApiAuthenticationResultSuccess;
   request: Request;
-  resource?: TResource;
+  resource?: TResource;
+  apiVersion: API_VERSIONS;
 }) => Promise<Response>;

Call site:

+      const apiVersion = getApiVersion(request);
       const result = await handler({
         params: parsedParams,
         searchParams: parsedSearchParams,
         headers: parsedHeaders,
         body: parsedBody,
         authentication: authenticationResult,
         request,
-        resource,
+        resource,
+        apiVersion,
       });

Also applies to: 714-722

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ada9a72 and f650eb3.

📒 Files selected for processing (14)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/deploys.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/orgs.ts (1 hunks)
  • packages/core/src/v3/isomorphic/dates.ts (1 hunks)
  • packages/core/src/v3/isomorphic/index.ts (1 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/webapp/app/routes/api.v1.runs.ts
  • packages/cli-v3/src/apiClient.ts
  • packages/cli-v3/src/commands/mcp.ts
  • packages/cli-v3/src/mcp/tools/deploys.ts
  • packages/cli-v3/src/mcp/tools/orgs.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/core/src/v3/isomorphic/index.ts
  • packages/core/src/v3/isomorphic/dates.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/cli-v3/src/mcp/context.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/src/v3/isomorphic/index.ts
  • packages/core/src/v3/isomorphic/dates.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Tasks must be exported, even subtasks in the same file.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-08-14T12:13:20.408Z
Learnt from: myftija
PR: triggerdotdev/trigger.dev#2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.408Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
🧬 Code Graph Analysis (4)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (2)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
packages/cli-v3/src/mcp/context.ts (6)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
packages/cli-v3/src/mcp/auth.ts (1)
  • mcpAuth (24-153)
packages/cli-v3/src/apiClient.ts (1)
  • CliApiClient (55-763)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/mcp/capabilities.ts (3)
  • hasRootsCapability (3-11)
  • hasSamplingCapability (13-21)
  • hasElicitationCapability (23-31)
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (5)
apps/webapp/app/routes/api.v1.orgs.ts (1)
  • loader (7-37)
apps/webapp/app/routes/api.v1.projects.ts (1)
  • loader (8-54)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/schemas/api.ts (6)
  • GetProjectsResponseBody (50-50)
  • GetProjectsResponseBody (52-52)
  • CreateProjectRequestBody (65-71)
  • CreateProjectRequestBody (73-73)
  • GetProjectResponseBody (30-46)
  • GetProjectResponseBody (48-48)
packages/cli-v3/src/apiClient.ts (1)
  • createProject (158-168)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
packages/cli-v3/src/mcp/context.ts (2)

123-156: Windows-safe absolute path check is correct

Good switch to path.isAbsolute(). This addresses the Windows path bug (e.g., C:\foo) previously raised and ensures correct cwd resolution.


16-22: Type-first options + named exports align with repo conventions

Using a type for McpContextOptions and avoiding default exports matches the stated guidelines. No changes needed.

Also applies to: 24-36

packages/core/src/v3/isomorphic/index.ts (1)

7-7: LGTM: Barrel re-export wired

Re-exporting dates.js is consistent with existing ESM-style .js paths in this barrel and cleanly surfaces parseDate.

apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1)

22-109: JWT issuance flow reads clean and consistent

Auth, param/body validation, environment resolution (including preview via x-trigger-branch), and signing look solid and align with the broader API patterns.

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

702-712: Nice: 404 guard for missing action resource

Adding the not-found check prevents handlers from receiving undefined when findResource is configured. This closes the gap with the loader path.

packages/core/src/v3/schemas/api.ts (1)

65-71: Good: stricter project name validation

Trimming and length bounds will prevent accidental whitespace-only names and extreme inputs.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (2)
packages/cli-v3/src/commands/update.ts (1)

36-41: Wrong command identifier passed to wrapCommandAction ("dev" vs "update")

This looks like a copy-paste oversight. It affects telemetry, logs, and any command-specific behavior derived from the identifier.

Apply this diff:

-    .action(async (path, options) => {
-      wrapCommandAction("dev", UpdateCommandOptions, options, async (opts) => {
+    .action(async (path, options) => {
+      wrapCommandAction("update", UpdateCommandOptions, options, async (opts) => {
         await printStandloneInitialBanner(true);
         await updateCommand(path, opts);
       });
     });
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

577-584: 413 early return bypasses CORS wrapper

When maxContentLength is hit, the 413 response is returned without wrapResponse(). If corsStrategy !== "none", browsers will receive a non-CORS response and treat it as a network error.

       if (maxContentLength) {
         const contentLength = request.headers.get("content-length");

         if (!contentLength || parseInt(contentLength) > maxContentLength) {
-          return json({ error: "Request body too large" }, { status: 413 });
+          return await wrapResponse(
+            request,
+            json({ error: "Request body too large" }, { status: 413 }),
+            corsStrategy !== "none"
+          );
         }
       }
♻️ Duplicate comments (15)
packages/cli-v3/src/mcp/auth.ts (1)

120-152: Persist and reuse the actual API base URL used during auth + rename variable (staging/self-hosted fix)

You construct apiClient with authConfig?.apiUrl ?? opts.defaultApiUrl, but later you persist and use opts.defaultApiUrl. This breaks non-default deployments (staging/self-hosted). Persist apiClient.apiURL and reuse it, and rename indexResult for clarity.

-  const indexResult = await pRetry(
+  const tokenResult = await pRetry(
     () => getPersonalAccessToken(apiClient, authorizationCodeResult.authorizationCode),
     {
       //this means we're polling, same distance between each attempt
       factor: 1,
       retries: 60,
       minTimeout: 1000,
     }
   );
 
-  writeAuthConfigProfile(
-    { accessToken: indexResult.token, apiUrl: opts.defaultApiUrl },
-    options?.profile
-  );
-
-  const client = new CliApiClient(opts.defaultApiUrl, indexResult.token);
+  const apiUrlUsed = apiClient.apiURL;
+  writeAuthConfigProfile(
+    { accessToken: tokenResult.token, apiUrl: apiUrlUsed },
+    options?.profile
+  );
+
+  const client = new CliApiClient(apiUrlUsed, tokenResult.token);
   const userData = await client.whoAmI();
 
   if (!userData.success) {
     throw new Error(userData.error);
   }
 
   return {
     ok: true as const,
     profile: options?.profile ?? "default",
     userId: userData.data.userId,
     email: userData.data.email,
     dashboardUrl: userData.data.dashboardUrl,
     auth: {
-      accessToken: indexResult.token,
-      apiUrl: opts.defaultApiUrl,
+      accessToken: tokenResult.token,
+      apiUrl: apiUrlUsed,
     },
   };
packages/cli-v3/src/mcp/tools/deploys.ts (2)

101-105: Dev-only guard blocks even “dev” environment; gate only non-dev environments

Align with the pattern used elsewhere: allow dev when --dev-only is set, block others.

Apply this diff:

-    if (ctx.options.devOnly) {
+    if (ctx.options.devOnly && input.environment !== "dev") {
       return respondWithError(
         `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
       );
     }

142-155: Spawning via npx is incorrect; process.argv doesn’t invoke npx

process.argv[0] is the node binary and process.argv[1] is the current script. This won’t run npx trigger.dev. Return explicit npx invocations.

Apply this diff:

-    return [process.argv[0] ?? "npx", process.argv[1] ?? "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
@@
-    if (typeof process.argv[0] === "string" && typeof process.argv[1] === "string") {
-      return [process.argv[0], process.argv[1]];
-    }
-
-    return ["npx", "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
packages/core/src/v3/schemas/api.ts (7)

32-36: Enforce externalRef format to match “starts with proj_” contract

Add a regex to validate the documented format.

-  externalRef: z
-    .string()
-    .describe(
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9_-]+$/, "Must start with proj_")
+    .describe(
       "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
     ),

85-93: Export the inferred type for GetWorkerTaskResponse

Schema is exported but its TypeScript type isn’t. Add the type alias for consumers.

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

94-103: Use RunEngineVersion for engine field

Keeps engine consistent across schemas and prevents arbitrary strings.

 export const GetWorkerByTagResponse = z.object({
   worker: z.object({
     id: z.string(),
     version: z.string(),
-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),
     sdkVersion: z.string().nullish(),
     cliVersion: z.string().nullish(),
     tasks: z.array(GetWorkerTaskResponse),
   }),
 });

1162-1168: Pagination limit: enforce integer and apply default 20 as described

The description promises a default of 20 and max 100, but no default is set and integers aren’t enforced.

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .min(1, "Limit must be at least 1")
-  .max(100, "Limit must be less than 100")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1, "Limit must be at least 1")
+  .max(100, "Limit must be less than 100")
+  .default(20)
+  .describe("The number of deployments to return, defaults to 20 (max 100)");

Note: With .default(20), the property can be omitted and will resolve to 20.


1169-1176: Name the plain shape distinctly from the Zod schema to avoid confusion

ApiDeploymentListParams is a plain object used inside z.object(). Rename to make the intent clear.

-export const ApiDeploymentListParams = {
+const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

1187-1206: Unify git metadata with GitMeta

Use the existing GitMeta schema for consistency and stronger typing.

 export const ApiDeploymentListResponseItem = z.object({
   id: z.string(),
   createdAt: z.coerce.date(),
   shortCode: z.string(),
   version: z.string(),
   runtime: z.string(),
   runtimeVersion: z.string(),
   status: z.enum([
     "PENDING",
     "BUILDING",
     "DEPLOYING",
     "DEPLOYED",
     "FAILED",
     "CANCELED",
     "TIMED_OUT",
   ]),
   deployedAt: z.coerce.date().optional(),
-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),
   error: DeploymentErrorData.optional(),
 });

1210-1221: Branch git metadata should also use GitMeta

Same rationale as deployments.

 export const ApiBranchListResponseBody = z.object({
   branches: z.array(
     z.object({
       id: z.string(),
       name: z.string(),
       createdAt: z.coerce.date(),
       updatedAt: z.coerce.date(),
-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),
       isPaused: z.boolean(),
     })
   ),
 });
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

706-712: Action 404 guard: great addition; align error payload with loader for consistency

Nice fix adding the 404 guard for action routes. To keep error shapes consistent with the loader path (which uses { error: "Not found" }), consider unifying the message.

-          json({ error: "Resource not found" }, { status: 404 }),
+          json({ error: "Not found" }, { status: 404 }),
packages/cli-v3/install-mcp.sh (2)

102-167: Extract repeated Node.js JSON-manipulation into a reusable helper to cut duplication

The inline Node -e block is near-identical across all installers. Centralize it in one bash helper function to simplify maintenance and ensure consistent behavior (error handling, pretty formatting, structure differences like servers vs mcpServers vs mcp).

Add this helper near the top of the script (after line 101) to standardize JSON updates:

# Function to update JSON config using Node.js
update_json_config() {
  local config_path="$1"
  local server_key="$2"   # e.g., "mcpServers", "servers", or "mcp"
  local entry_key="trigger"
  local type="$3"         # optional: "stdio" for Crush
  node -e "
    const fs = require('fs');
    const configPath = '$config_path';
    const nodePath = '$NODE_PATH';
    const cliPath = '$CLI_PATH';
    const logFile = '$MCP_LOG_FILE';
    const serverKey = '$server_key';
    const type = '$type';

    try {
      let config;
      try {
        const content = fs.readFileSync(configPath, 'utf8');
        config = JSON.parse(content);
      } catch {
        console.log('📝 Creating new configuration structure...');
        config = {};
      }

      if (!config[serverKey]) config[serverKey] = {};
      const entry = {
        command: nodePath,
        args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', process.env.API_URL || 'http://localhost:3030'],
      };
      if (serverKey === 'mcp' || type === 'stdio') entry.type = 'stdio';

      // Special-case Crush which uses `mcp` root and `trigger` key under it
      config[serverKey][entry_key] = entry;

      fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
      console.log('✅ Successfully installed Trigger.dev MCP server');
    } catch (error) {
      console.error('❌ Error updating configuration:', error.message);
      process.exit(1);
    }
  "
}

Then replace this block with a one-liner:

-    node -e "
-    const fs = require('fs');
-    const path = require('path');
-    ...
-    "
+    update_json_config "$CLAUDE_CONFIG" "mcpServers"

Repeat similarly for:

  • Claude Desktop: update_json_config "$CLAUDE_DESKTOP_CONFIG" "mcpServers"
  • Cursor: update_json_config "$CURSOR_CONFIG" "mcpServers"
  • VS Code: update_json_config "$VSCODE_CONFIG" "servers"
  • Crush: update_json_config "$CRUSH_CONFIG" "mcp" "stdio"
  • Windsurf: update_json_config "$WINDSURF_CONFIG" "mcpServers"

169-241: DRY the informational logs to avoid divergence across targets

The success message blocks are nearly identical. Consider emitting a small helper function that prints the “Config file / Node.js path / CLI path” block and a per-client hint, to keep messaging consistent and maintainable.

Would you like me to push a refactor that adds print_success_summary "<Client Name>" "<Config Path>" and uses it in all installers?

Also applies to: 244-317, 319-392, 394-471, 473-546

packages/cli-v3/src/commands/install-mcp.ts (1)

386-412: Normalize path components and remove silent break in applyConfigToExistingConfig

Breaking the loop on falsy segments can silently skip writing config. Filter empties before iterating and remove the in-loop break. This mirrors a prior review suggestion and makes the function robust to odd inputs.

 function applyConfigToExistingConfig(
   existingConfig: any,
   pathComponents: string[],
   config: McpServerConfig
 ) {
-  const clonedConfig = structuredClone(existingConfig);
+  const clonedConfig = structuredClone(existingConfig);
 
-  let currentValueAtPath = clonedConfig;
+  // Remove empty/invalid segments up front
+  const segments = pathComponents.filter((s) => !!s && s.trim().length > 0);
+  if (segments.length === 0) return clonedConfig;
+
+  let currentValueAtPath = clonedConfig;
 
-  for (let i = 0; i < pathComponents.length; i++) {
-    const currentPathSegment = pathComponents[i];
-
-    if (!currentPathSegment) {
-      break;
-    }
-
-    if (i === pathComponents.length - 1) {
+  for (let i = 0; i < segments.length; i++) {
+    const currentPathSegment = segments[i]!;
+    if (i === segments.length - 1) {
       currentValueAtPath[currentPathSegment] = config;
       break;
     } else {
       currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
       currentValueAtPath = currentValueAtPath[currentPathSegment];
     }
   }
 
   return clonedConfig;
 }
packages/cli-v3/src/mcp/tools/orgs.ts (1)

144-147: Previous concern resolved: respects provided projectRef

The flow now correctly uses a supplied projectRef instead of always creating a new project.

🧹 Nitpick comments (24)
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (1)

116-124: Guard against unknown/empty client names in instruction text

Avoid rendering “unknown” or empty client names. Minor copy-hardening makes the success message always read well.

-function getInstructionsForSource(source: string, clientName: string) {
+function getInstructionsForSource(source: string, clientName: string) {
   if (source === "mcp") {
-    if (clientName) {
+    if (clientName && clientName !== "unknown") {
       return `Return to your ${prettyClientNames[clientName] ?? clientName} to continue.`;
     }
   }
 
   return `Return to your terminal to continue.`;
 }
packages/cli-v3/src/mcp/auth.ts (3)

95-104: Client name extraction is best-effort; consider normalizing known variants (optional)

getClientVersion()?.name can vary (“VS Code”, “Visual Studio Code”, etc.). If you want consistent UX server-side, consider normalizing here (e.g., map common aliases to canonical keys used by the web route).


116-118: Optional: Fallback if opening the browser fails

If open() rejects (headless/unsupported environment), consider printing the URL to the user or returning a structured error your MCP client can display.

-  await open(url.toString());
+  try {
+    await open(url.toString());
+  } catch {
+    // Optionally: log and still proceed with polling so users can manually visit the URL
+    // e.g., server.server?.log or context logger if available
+  }

17-22: Nit: Unused context option

context: McpContext is defined but not used. Remove it or wire it (e.g., for logging or user prompts) to reduce noise.

packages/cli-v3/src/commands/update.ts (2)

331-367: Add lightweight caching and optional direct package.json resolution for performance

Resolving each dependency via nodeResolve.sync and then reading package.json can be costly across many @trigger.dev/* packages. A small in-memory cache and an optional fast path to resolve ${name}/package.json first will reduce repeated I/O and resolution work.

Apply this diff inside the function to add caching:

-export async function tryResolveTriggerPackageVersion(
-  name: string,
-  basedir?: string
-): Promise<string | undefined> {
+export async function tryResolveTriggerPackageVersion(
+  name: string,
+  basedir?: string
+): Promise<string | undefined> {
+  const cacheKey = `${name}::${basedir ?? ""}`;
+  const cached = _resolvedVersionCache.get(cacheKey);
+  if (cached !== undefined) return cached;
   try {
-    const resolvedPath = nodeResolve.sync(name, {
+    // Fast path: try to resolve the package.json directly (may be disallowed by exports, so wrap in try)
+    let resolvedPath: string;
+    try {
+      resolvedPath = nodeResolve.sync(join(name, "package.json"), { basedir });
+    } catch {
+      resolvedPath = nodeResolve.sync(name, {
         basedir,
-    });
+      });
+    }
 
     logger.debug(`Resolved ${name} package version path`, { name, resolvedPath });
 
     const { packageJson } = await getPackageJson(dirname(resolvedPath), {
       test: (filePath) => {
         // We need to skip any type-marker files
-        if (filePath.includes("dist/commonjs")) {
+        if (filePath.includes(join("dist", "commonjs"))) {
           return false;
         }
 
-        if (filePath.includes("dist/esm")) {
+        if (filePath.includes(join("dist", "esm"))) {
           return false;
         }
 
         return true;
       },
     });
 
     if (packageJson.version) {
       logger.debug(`Resolved ${name} package version`, { name, version: packageJson.version });
-      return packageJson.version;
+      _resolvedVersionCache.set(cacheKey, packageJson.version);
+      return packageJson.version;
     }
 
-    return;
+    _resolvedVersionCache.set(cacheKey, undefined);
+    return;
   } catch (error) {
     logger.debug("Failed to resolve package version", { name, error });
+    _resolvedVersionCache.set(cacheKey, undefined);
     return undefined;
   }
 }

Add this supporting snippet at module scope (outside the selected range):

// Module-scope cache for resolved versions to avoid repeated resolution work within a single CLI run
const _resolvedVersionCache = new Map<string, string | undefined>();

Note: The direct ${name}/package.json resolution is opportunistic and falls back cleanly when blocked by "exports".


296-329: Resolve versions in parallel and avoid recomputing basedir inside the loop

Currently each dependency resolve happens sequentially, which can be slow on large projects. Parallelizing and reusing the computed basedir improves performance without changing behavior.

Apply this diff:

-async function getTriggerDependencies(
-  packageJson: PackageJson,
-  packageJsonPath: string
-): Promise<Dependency[]> {
-  const deps: Dependency[] = [];
-
-  for (const type of ["dependencies", "devDependencies"] as const) {
-    for (const [name, version] of Object.entries(packageJson[type] ?? {})) {
-      if (!version) {
-        continue;
-      }
-
-      if (version.startsWith("workspace")) {
-        continue;
-      }
-
-      if (!triggerPackageFilter.test(name)) {
-        continue;
-      }
-
-      const ignoredPackages = ["@trigger.dev/companyicons"];
-
-      if (ignoredPackages.includes(name)) {
-        continue;
-      }
-
-      const $version = await tryResolveTriggerPackageVersion(name, dirname(packageJsonPath));
-
-      deps.push({ type, name, version: $version ?? version });
-    }
-  }
-
-  return deps;
-}
+async function getTriggerDependencies(
+  packageJson: PackageJson,
+  packageJsonPath: string
+): Promise<Dependency[]> {
+  const baseDir = dirname(packageJsonPath);
+  const ignoredPackages = new Set(["@trigger.dev/companyicons"]);
+
+  const entries = (["dependencies", "devDependencies"] as const).flatMap((type) =>
+    Object.entries(packageJson[type] ?? {})
+      .filter(([name, version]) => {
+        if (!version) return false;
+        if ((version as string).startsWith("workspace")) return false;
+        if (!triggerPackageFilter.test(name)) return false;
+        if (ignoredPackages.has(name)) return false;
+        return true;
+      })
+      .map(([name, version]) => ({ type, name, version: version as string }))
+  );
+
+  const resolved = await Promise.all(
+    entries.map(async ({ type, name, version }) => {
+      const $version = await tryResolveTriggerPackageVersion(name, baseDir);
+      return { type, name, version: $version ?? version } as Dependency;
+    })
+  );
+
+  return resolved;
+}
packages/cli-v3/src/mcp/tools/docs.ts (2)

10-13: Deduplicate input validation and strengthen constraints; export a Zod object as schema.

Define the shape once, reuse it for both the exported inputSchema and the toolHandler, and ensure non-empty queries. This avoids drift and improves UX.

 export const searchDocsTool = {
   name: toolsMetadata.search_docs.name,
   title: toolsMetadata.search_docs.title,
   description: toolsMetadata.search_docs.description,
-  inputSchema: {
-    query: z.string(),
-  },
-  handler: toolHandler({ query: z.string() }, async (input, { ctx }) => {
+  inputSchema: z.object({
+    query: z.string().trim().min(1, "query must not be empty"),
+  }),
+  handler: toolHandler(
+    { query: z.string().trim().min(1, "query must not be empty") },
+    async (input, { ctx }) => {
       ctx.logger?.log("calling search_docs", { input });
 
       const results = await performSearch(input.query);
 
       return {
         content: [{ type: "text", text: results.result }],
       };
-  }),
+    }
+  ),
 };

If you prefer zero duplication, hoist the shape:

// place above export const searchDocsTool
const searchDocsInputShape = {
  query: z.string().trim().min(1, "query must not be empty"),
};
const searchDocsInputSchema = z.object(searchDocsInputShape);

Then use:

  • inputSchema: searchDocsInputSchema
  • handler: toolHandler(searchDocsInputShape, async (input, { ctx }) => { … })

13-21: Wrap network call with try/catch and normalize the response.

A failed fetch will throw and bubble out as an unstructured error. Catch and return a tool-friendly error payload; also guard against non-string results.

-  handler: toolHandler({ query: z.string() }, async (input, { ctx }) => {
-    ctx.logger?.log("calling search_docs", { input });
-
-    const results = await performSearch(input.query);
-
-    return {
-      content: [{ type: "text", text: results.result }],
-    };
-  }),
+  handler: toolHandler({ query: z.string() }, async (input, { ctx }) => {
+    ctx.logger?.log("calling search_docs", { input });
+    try {
+      const results = await performSearch(input.query);
+      const text =
+        typeof (results as any)?.result === "string"
+          ? (results as any).result
+          : JSON.stringify(results, null, 2);
+      return {
+        content: [{ type: "text", text }],
+      };
+    } catch (err) {
+      const message = err instanceof Error ? err.message : String(err);
+      ctx.logger?.error?.("search_docs failed", { error: message });
+      return {
+        content: [{ type: "text", text: `search_docs failed: ${message}` }],
+      };
+    }
+  }),
packages/cli-v3/src/mcp/tools/deploys.ts (1)

134-135: Nit: prefer process.execPath over argv[0] for Node path

execPath is the absolute path to the Node executable and more robust across environments.

-    return [process.argv[0] ?? "node", installedCLI.path];
+    return [process.execPath, installedCLI.path];
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4)

433-452: Make action options feature-parity: add shouldRetryNotFound and enrich findResource signature

  • Add shouldRetryNotFound?: boolean to action options for parity with loader’s “retry hint” header.
  • Consider passing headers/body to findResource (action-only), which you already parse before resolution. This enables cases where the resource identifier is in the request body or headers.
 type ApiKeyActionRouteBuilderOptions<
   TParamsSchema extends AnyZodSchema | undefined = undefined,
   TSearchParamsSchema extends AnyZodSchema | undefined = undefined,
   THeadersSchema extends AnyZodSchema | undefined = undefined,
   TBodySchema extends AnyZodSchema | undefined = undefined,
   TResource = never
 > = {
   params?: TParamsSchema;
   searchParams?: TSearchParamsSchema;
   headers?: THeadersSchema;
   allowJWT?: boolean;
   corsStrategy?: "all" | "none";
   method?: "POST" | "PUT" | "DELETE" | "PATCH";
+  shouldRetryNotFound?: boolean;
-  findResource?: (
+  findResource?: (
     params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TParamsSchema>
       : undefined,
     authentication: ApiAuthenticationResultSuccess,
     searchParams: TSearchParamsSchema extends
       | z.ZodFirstPartySchemaTypes
       | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TSearchParamsSchema>
-      : undefined
+      : undefined,
+    headers: THeadersSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
+      ? z.infer<THeadersSchema>
+      : undefined,
+    body: TBodySchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
+      ? z.infer<TBodySchema>
+      : undefined
   ) => Promise<TResource | undefined>;

Follow-up in the 404 branch (see separate comment on lines 706-712) to add the retry header once shouldRetryNotFound is introduced.


702-705: Pass parsed headers/body into findResource (action-only) to enable richer resolution

Since action parsing already computed parsedHeaders and parsedBody, passing them into findResource unlocks more use-cases (e.g., IDs in headers or body).

-      const resource = options.findResource
-        ? await options.findResource(parsedParams, authenticationResult, parsedSearchParams)
-        : undefined;
+      const resource = options.findResource
+        ? await options.findResource(
+            parsedParams,
+            authenticationResult,
+            parsedSearchParams,
+            parsedHeaders,
+            parsedBody
+          )
+        : undefined;

706-712: Optional: include retry hint header (parity with loader) when resource is missing

If you adopt shouldRetryNotFound in action options, mirror the loader’s retry hint header.

-        return await wrapResponse(
-          request,
-          json({ error: "Not found" }, { status: 404 }),
-          corsStrategy !== "none"
-        );
+        return await wrapResponse(
+          request,
+          json(
+            { error: "Not found" },
+            {
+              status: 404,
+              headers: { "x-should-retry": options.shouldRetryNotFound ? "true" : "false" },
+            }
+          ),
+          corsStrategy !== "none"
+        );

481-501: Type-level refinement: make resource required when findResource is provided

Runtime guarantees ensure that when findResource exists, resource is non-null (404 otherwise). The handler arg still types resource?: TResource, forcing unnecessary undefined checks.

Two approaches:

  • Overloads for createActionApiRoute:
    • Overload A (no findResource): handler receives resource?: TResource
    • Overload B (with findResource): handler receives resource: NonNullable<TResource>
  • Or define a helper conditional type on the options generic to derive the handler’s resource arg.

This improves DX and prevents accidental null-check omissions without changing runtime behavior.

packages/cli-v3/install-mcp.sh (3)

80-88: Don’t hardcode API URL; allow override via env or flag for non-local installs

Every target injects --api-url http://localhost:3030. That’s great for local dev, but it’s surprising for users pointing at prod or a different environment. Provide an overridable API URL via environment variable (e.g., API_URL) with sane default.

Apply this diff to define API_URL once:

 # Construct the path to the CLI index.js file
 CLI_PATH="$SCRIPT_DIR/dist/esm/index.js"

 # Construct the path to the MCP log file
 MCP_LOG_FILE="$SCRIPT_DIR/.mcp.log"
 
+# Default API URL (override with: API_URL="https://api.trigger.dev" ./install-mcp.sh)
+API_URL="${API_URL:-http://localhost:3030}"

Then replace the hardcoded URL in each Node JSON update (all installers) with ${API_URL}:

-            args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', 'http://localhost:3030']
+            args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', process.env.API_URL || 'http://localhost:3030']

Note: Because these values are injected into the Node -e script at runtime, you can reference process.env.API_URL safely.

Also applies to: 119-149, 221-224, 295-299, 371-374, 447-452, 523-527


90-98: chmod on an ESM file not necessary

index.js is launched via node in the generated configs, so it needn’t be executable. Safe to remove chmod +x "$CLI_PATH" to avoid implying it’s a CLI entrypoint.

-# Ensure the CLI is executable
-chmod +x "$CLI_PATH"

171-188: Avoid requiring ‘path’ in Node snippets when unused

Each inline Node -e block includes const path = require('path'); but doesn’t use it. Small cleanup.

-    const path = require('path');

Also applies to: 254-262, 335-339, 489-494

packages/cli-v3/src/commands/install-mcp.ts (4)

109-118: Align default tag with CLI option default to avoid surprises

Commander sets the default tag to cliTag (“v4-beta” or “latest”), but the zod schema defaults to cliVersion. This can lead to different behavior when calling installMcpServer() programmatically versus via CLI.

-  tag: z.string().default(cliVersion),
+  tag: z.string().default(cliTag),

137-143: Update --scope help text to include ‘local’ where supported

The help says “either user or project” but scopes also include “local”. Clarify to reduce confusion.

-    .option("--scope <scope>", "Choose the scope of the MCP server, either user or project")
+    .option("--scope <scope>", "Choose the scope of the MCP server: user, project, or local (where supported)")

201-248: Type inference for results could be explicit for clarity

const results = []; infers any[]. Consider annotating to the declared InstallMcpServerResults for stronger typing.

-  const results = [];
+  const results: InstallMcpServerResults = [];

465-554: Consider surfacing --log-level and --project-ref in the generated config preview for unsupported clients

You already add these flags into the args array. It may help UX to echo a concrete JSON block that includes them (as you do) and to provide a one-liner npx example. Minor, but improves copy-paste experience.

packages/cli-v3/src/mcp/tools/orgs.ts (4)

155-178: Config-file branch: consider a more helpful resolution when project ref is missing

Currently, if a config file exists but no valid project ref is found, you return an error. Consider offering a remediation: either prompt to provide projectRef or proceed to create a new project (with an explicit confirmation), to smooth the UX.


144-201: Nit: avoid duplicate getCliApiClient calls

You initialize the CLI API client twice in this handler. Hoist it once and reuse.

Apply:

-    let projectRef: string | undefined = input.projectRef;
+    let projectRef: string | undefined = input.projectRef;
+    const cliApiClient = await ctx.getCliApiClient();
@@
-      const cliApiClient = await ctx.getCliApiClient();
+      // cliApiClient already initialized above
@@
-    const cliApiClient = await ctx.getCliApiClient();
+    // reuse cliApiClient initialized above

206-219: Surface missing dev env details to the user instead of silently using placeholders

If getProjectEnv fails, the guide contains placeholders without context. Add a short warning preface.

Apply:

-    const manualSetupGuide = await getManualSetupGuide(
+    const preface = projectEnv.success
+      ? ""
+      : "Warning: Could not retrieve the dev API key or API URL for the provided projectRef. The guide below contains placeholders. Visit the dashboard or run `trigger.dev env get --project <projectRef> --env dev` to fetch real values.";
+    const manualSetupGuide = await getManualSetupGuide(
       projectRef,
       projectEnv.success ? projectEnv.data.apiKey : undefined,
       projectEnv.success ? projectEnv.data.apiUrl : undefined
     );
 
     return {
       content: [
         {
           type: "text",
-          text: manualSetupGuide,
+          text: [preface, manualSetupGuide].filter(Boolean).join("\n\n"),
         },
       ],
     };

223-233: Use replaceAll for all occurrences and add fetch fallback

replace only updates the first occurrence; use replaceAll for robustness. Also add a try/catch to gracefully handle network failures.

Apply:

-async function getManualSetupGuide(projectRef: string, apiKey?: string, apiUrl?: string) {
-  const response = await fetch("https://trigger.dev/docs/manual-setup.md");
-  let text = await response.text();
-
-  text = text.replace("<your-project-ref>", projectRef);
-
-  text = text.replace("tr_dev_xxxxxxxxxx", apiKey ?? "tr_dev_xxxxxxxxxx");
-  text = text.replace(
-    "https://your-trigger-instance.com",
-    apiUrl ?? "https://your-trigger-instance.com"
-  );
+async function getManualSetupGuide(projectRef: string, apiKey?: string, apiUrl?: string) {
+  let text: string;
+  try {
+    const response = await fetch("https://trigger.dev/docs/manual-setup.md");
+    text = await response.text();
+  } catch {
+    text =
+      "# Manual setup\n\n" +
+      "Unable to fetch the online manual setup guide. Using a local fallback. You can try again later or visit https://trigger.dev/docs/manual-setup.\n";
+  }
+
+  text = text.replaceAll("<your-project-ref>", projectRef);
+  text = text.replaceAll("tr_dev_xxxxxxxxxx", apiKey ?? "tr_dev_xxxxxxxxxx");
+  text = text.replaceAll(
+    "https://your-trigger-instance.com",
+    apiUrl ?? "https://your-trigger-instance.com"
+  );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f650eb3 and 5313b53.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (52)
  • .cursor/mcp.json (1 hunks)
  • .gitignore (1 hunks)
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (5 hunks)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.orgs.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (1 hunks)
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1 hunks)
  • apps/webapp/app/services/authorization.server.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/install-mcp.sh (1 hunks)
  • packages/cli-v3/package.json (2 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/cli/common.ts (1 hunks)
  • packages/cli-v3/src/cli/index.ts (2 hunks)
  • packages/cli-v3/src/commands/deploy.ts (1 hunks)
  • packages/cli-v3/src/commands/dev.ts (2 hunks)
  • packages/cli-v3/src/commands/init.ts (9 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/login.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/dev/devOutput.ts (2 hunks)
  • packages/cli-v3/src/mcp/auth.ts (1 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/config.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/logger.ts (1 hunks)
  • packages/cli-v3/src/mcp/mintlifyClient.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/deploys.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/docs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/orgs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/previewBranches.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/runs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/tasks.ts (1 hunks)
  • packages/cli-v3/src/mcp/types.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • packages/core/src/v3/apiClient/index.ts (3 hunks)
  • packages/core/src/v3/isomorphic/dates.ts (1 hunks)
  • packages/core/src/v3/isomorphic/index.ts (1 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (42)
  • apps/webapp/app/routes/api.v1.runs.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • packages/cli-v3/src/mcp/config.ts
  • .gitignore
  • packages/cli-v3/src/cli/common.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • packages/cli-v3/src/commands/deploy.ts
  • apps/webapp/app/services/authorization.server.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • packages/cli-v3/src/dev/devOutput.ts
  • packages/cli-v3/src/mcp/capabilities.ts
  • packages/cli-v3/src/mcp/mintlifyClient.ts
  • packages/cli-v3/src/commands/dev.ts
  • packages/cli-v3/src/cli/index.ts
  • .cursor/mcp.json
  • packages/core/src/v3/isomorphic/index.ts
  • packages/cli-v3/src/mcp/logger.ts
  • packages/cli-v3/src/mcp/tools/runs.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • packages/cli-v3/package.json
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/cli-v3/src/mcp/context.ts
  • packages/cli-v3/src/apiClient.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • packages/cli-v3/src/utilities/fileSystem.ts
  • packages/cli-v3/src/commands/login.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • packages/cli-v3/src/mcp/schemas.ts
  • packages/core/src/v3/isomorphic/dates.ts
  • packages/cli-v3/src/mcp/tools/previewBranches.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • packages/core/src/v3/apiClient/index.ts
  • packages/cli-v3/src/mcp/utils.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • packages/cli-v3/src/mcp/types.ts
  • packages/cli-v3/src/mcp/tools.ts
  • packages/cli-v3/src/commands/mcp.ts
  • packages/cli-v3/src/commands/init.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/cli-v3/src/mcp/tools/tasks.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/tools/docs.ts
  • packages/cli-v3/src/mcp/tools/orgs.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/commands/install-mcp.ts
  • packages/cli-v3/src/mcp/auth.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/cli-v3/src/mcp/tools/deploys.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (4)
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : ALWAYS generate Trigger.dev tasks using the `task` function from `trigger.dev/sdk/v3` and export them as shown in the correct pattern.

Applied to files:

  • packages/cli-v3/src/commands/update.ts
📚 Learning: 2025-07-18T17:50:25.014Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-07-18T17:50:25.014Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Tasks must be exported, even subtasks in the same file.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-08-14T12:13:20.408Z
Learnt from: myftija
PR: triggerdotdev/trigger.dev#2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.408Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
PR: triggerdotdev/trigger.dev#2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
🧬 Code Graph Analysis (5)
packages/cli-v3/src/mcp/tools/docs.ts (3)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/utils.ts (1)
  • toolHandler (40-53)
packages/cli-v3/src/mcp/mintlifyClient.ts (1)
  • performSearch (1-16)
packages/cli-v3/src/mcp/tools/orgs.ts (6)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/types.ts (1)
  • ToolMeta (5-7)
packages/cli-v3/src/mcp/utils.ts (2)
  • respondWithError (6-16)
  • toolHandler (40-53)
packages/core/src/v3/schemas/api.ts (2)
  • GetProjectsResponseBody (50-50)
  • GetProjectsResponseBody (52-52)
packages/cli-v3/src/mcp/schemas.ts (4)
  • CreateProjectInOrgInput (15-22)
  • CreateProjectInOrgInput (24-24)
  • InitializeProjectInput (26-39)
  • InitializeProjectInput (41-41)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/commands/install-mcp.ts (10)
packages/core/src/v3/index.ts (1)
  • VERSION (85-85)
packages/cli-v3/src/cli/index.ts (1)
  • program (20-20)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • writeConfigHasSeenMCPInstallPrompt (111-118)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
apps/webapp/memory-leak-detector.js (3)
  • args (883-883)
  • arg (887-887)
  • i (886-886)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
scripts/unpack-worker.js (1)
  • fullPath (52-52)
packages/cli-v3/src/utilities/fileSystem.ts (5)
  • expandTilde (54-68)
  • safeReadJSONCFile (139-147)
  • writeJSONFile (90-92)
  • safeReadTomlFile (125-133)
  • writeTomlFile (135-137)
packages/cli-v3/src/mcp/auth.ts (8)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (24-187)
packages/cli-v3/src/utilities/session.ts (2)
  • LoginResult (19-28)
  • LoginResultOk (7-17)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/utilities/isPersonalAccessToken.ts (1)
  • NotPersonalAccessTokenError (7-12)
packages/cli-v3/src/apiClient.ts (3)
  • CliApiClient (55-763)
  • createAuthorizationCode (69-77)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readAuthConfigProfile (92-102)
  • writeAuthConfigProfile (81-90)
packages/cli-v3/src/commands/login.ts (1)
  • getPersonalAccessToken (349-378)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (14)
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (1)

89-90: LGTM: Tailored post-auth instruction improves UX

Using getInstructionsForSource(result.source, result.clientName) to tailor guidance based on MCP/CLI context is a nice touch.

packages/cli-v3/src/commands/update.ts (1)

322-325: Good change: using the directory as basedir for resolution

Passing dirname(packageJsonPath) to the resolver is correct and avoids mis-resolving from the file path itself. This should improve accuracy especially in monorepos.

packages/cli-v3/src/mcp/tools/docs.ts (2)

7-9: Good: centralizing tool metadata.

Sourcing name/title/description from toolsMetadata keeps the registry consistent.


6-22: searchDocsTool is consistent with existing tools

  • Other tools either use an inline raw Zod shape ({} for no-input) or reference a schema’s .shape. Using { query: z.string() } inline here matches that pattern.
  • There is no shared ToolDefinition or registry type to annotate against—your tool object shape will be validated at runtime by toolHandler and caught by the global try/catch in registerTools.
  • respondWithError is defined in utils.ts and will be invoked automatically on parse failures or uncaught exceptions, so no additional error-branch wiring is required.

No changes needed.

packages/cli-v3/src/mcp/tools/deploys.ts (2)

20-24: Confirm intended behavior: deploy disallowed under --dev-only

Here you block deploys outright when devOnly is set. If the intention is parity with list_deploys (allow dev, block non-dev), consider gating by environment instead.

Do you want deploy to be fully disabled under --dev-only, or only for non-dev environments? I can update the guard to match your policy.


64-71: Verify tinyexec options object shape
Depending on the installed tinyexec version (check your package.json), cwd/env may need to live at the top level of the options object rather than under nodeOptions. If they’re in the wrong place, they’ll be ignored.

• File: packages/cli-v3/src/mcp/tools/deploys.ts
• Lines: 64–71
• Current snippet:

const deployProcess = x(nodePath, [cliPath, ...args], {
  nodeOptions: {
    cwd: cwd.cwd,
    env: {
      TRIGGER_MCP_SERVER: "1",
    },
  },
});

Other calls in apps/coordinator/src/exec.ts pass options like { ignoreAbort: true } at the top level, suggesting tinyexec expects flags there. Please confirm against the tinyexec README for your pinned version to ensure your cwd/env config isn’t accidentally ignored.

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)

721-722: Handler resource threading looks correct

resource is passed into the handler after the 404 guard. This aligns with the updated handler signature.


507-523: No explicit generic call sites detected; reorder is safe

I ran the suggested searches across the repo and only found the definitions of createActionApiRoute, ApiKeyActionRouteBuilderOptions, and ApiKeyActionHandlerFunction—no explicit <…> invocations in call sites. Reordering TBodySchema before TResource should not break any existing code.

packages/cli-v3/src/commands/install-mcp.ts (1)

201-217: Remove redundant clack version check for log.step

We already call log.step in many places (deploy.ts, dev.ts, init.ts, etc.), which confirms our pinned @clack/prompts version exposes log.step. No runtime issue will occur here—no change needed.

packages/cli-v3/src/mcp/tools/orgs.ts (5)

10-36: list_orgs tool: solid flow and error handling

Clear logging, proper API call, and standardized error response. Output formatting is concise and useful.


56-77: Project grouping by organization is correct and typesafe

Nice use of GetProjectsResponseBody element types to keep the accumulator well-typed.


79-103: Projects listing output is readable and useful

Well-structured per-org sections; includes key identifiers (projectRef, slug, createdAt).


106-134: Create-project tool implementation looks good

Input parsing via toolHandler, clear logging, API call, error handling, and succinct success message all check out.


14-15: Clarify inputSchema contract for no-input tools

The list_orgs and list_projects tools in packages/cli-v3/src/mcp/tools/orgs.ts (around lines 14–15 and 42–43) currently declare

inputSchema: {},
handler: async (input: unknown, { ctx }: ToolMeta) => {  }

whereas almost every other tool supplies a Zod raw shape (e.g. { query: z.string() } or SomeInput.shape) and is wrapped via toolHandler(shape, handler). Since context.server.registerTool likely converts Zod shapes into JSON Schema under the hood, passing a plain {} could produce an unexpected or invalid schema.

Please verify how inputSchema is consumed by McpServer.registerTool. If it expects Zod shapes, consider one of the following refactors for consistency:

  • Wrap with toolHandler and an empty raw shape:

    export const listOrgsTool = {
      name: toolsMetadata.list_orgs.name,
      title: toolsMetadata.list_orgs.title,
      description: toolsMetadata.list_orgs.description,
    - inputSchema: {},
    - handler: async (input: unknown, { ctx }: ToolMeta): Promise<CallToolResult> => {
    + inputSchema: {},
    + handler: toolHandler({}, async (_input, { ctx }) => {
        ctx.logger?.log("calling list_orgs", { input });
        …
      }),
    };
  • Or explicitly use Zod’s empty object shape:

    import { z } from "zod";
    
    export const listOrgsTool = {,
      inputSchema: z.object({}).shape,
      handler: toolHandler(z.object({}).shape, async (_input, { ctx }) => {
        ctx.logger?.log("calling list_orgs", { input });
        
      }),
    };

Either approach aligns these no-input tools with the rest of the MCP toolkit.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (21)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)

15-17: Mark 401 responses as non-cacheable.

Add Cache-Control: no-store to prevent caching of auth failures.

-  if (!authenticationResult) {
-    return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
-  }
+  if (!authenticationResult) {
+    return json(
+      { error: "Invalid or Missing Access Token" },
+      { status: 401, headers: { "Cache-Control": "no-store" } }
+    );
+  }

21-23: Mark 400 responses as non-cacheable.

Validation errors should also be no-store to avoid stale API results.

-  if (!parsedParams.success) {
-    return json({ error: "Invalid Params" }, { status: 400 });
-  }
+  if (!parsedParams.success) {
+    return json(
+      { error: "Invalid Params" },
+      { status: 400, headers: { "Cache-Control": "no-store" } }
+    );
+  }

40-42: Mark not-found responses as non-cacheable.

404s for project/env should also include no-store.

-  if (!project) {
-    return json({ error: "Project not found" }, { status: 404 });
-  }
+  if (!project) {
+    return json(
+      { error: "Project not found" },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }
@@
-  if (!envResult.success) {
-    return json({ error: envResult.error }, { status: 404 });
-  }
+  if (!envResult.success) {
+    return json(
+      { error: envResult.error },
+      { status: 404, headers: { "Cache-Control": "no-store" } }
+    );
+  }

Also applies to: 50-52


56-59: Harden devPresence lookup and disable caching on the status.

Redis outages can throw. Wrap in try/catch, return 503 on failure, and add no-store on both paths.

-  const isConnected = await devPresence.isConnected(runtimeEnv.id);
-
-  return json({ isConnected });
+  try {
+    const isConnected = await devPresence.isConnected(runtimeEnv.id);
+    return json({ isConnected }, { headers: { "Cache-Control": "no-store" } });
+  } catch {
+    return json(
+      { error: "Dev presence service unavailable" },
+      { status: 503, headers: { "Cache-Control": "no-store" } }
+    );
+  }
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

702-705: Nice: 404 guard added for missing resources in action routes.

This addresses the earlier feedback about preventing handlers from receiving an undefined resource. Good addition.

Also applies to: 706-712

packages/cli-v3/src/mcp/mintlifyClient.ts (1)

31-61: SSE reader leaks and unreachable throw; handle end-of-stream and cancel the reader.

The loop never cancels/releases the reader, the final throw is unreachable, and done === true with a trailing complete event isn’t processed. Cancel the stream in a finally, parse a trailing event on done, and tolerate whitespace before data:.

-// Get the first data: event and return the parsed JSON of the event
-async function parseSSEResponse(response: Response) {
-  const reader = response.body?.getReader();
-  const decoder = new TextDecoder();
-
-  if (!reader) {
-    throw new Error("No reader found");
-  }
-
-  let buffer = "";
-
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) throw new Error("SSE stream closed before data arrived");
-
-    buffer += decoder.decode(value, { stream: true });
-    const events = buffer.split("\n\n"); // SSE delimiter
-    buffer = events.pop()!; // keep incomplete
-
-    for (const evt of events) {
-      for (const line of evt.split("\n")) {
-        if (line.startsWith("data:")) {
-          const json = line.slice(5).trim();
-          return JSON.parse(json); // ✅ got it
-        }
-      }
-    }
-  }
-
-  throw new Error("No data: event found");
-}
+// Get the first data: event and return the parsed JSON of the event
+async function parseSSEResponse(response: Response) {
+  const reader = response.body?.getReader();
+  const decoder = new TextDecoder();
+
+  if (!reader) {
+    throw new Error("No reader found");
+  }
+
+  let buffer = "";
+
+  try {
+    while (true) {
+      const { value, done } = await reader.read();
+      if (done) {
+        // Try to parse any trailing buffered event before failing
+        const trailing = buffer.trim();
+        if (trailing.length > 0) {
+          const events = trailing.split("\n\n"); // SSE delimiter
+          for (const evt of events) {
+            const line = evt
+              .split("\n")
+              .map((l) => l.trimStart())
+              .find((l) => l.startsWith("data:"));
+            if (line) {
+              const json = line.slice(5).trim();
+              return JSON.parse(json);
+            }
+          }
+        }
+        throw new Error("SSE stream closed before data arrived");
+      }
+
+      buffer += decoder.decode(value, { stream: true });
+      const events = buffer.split("\n\n"); // SSE delimiter
+      buffer = events.pop() ?? ""; // keep incomplete
+
+      for (const evt of events) {
+        for (const line of evt.split("\n")) {
+          const trimmed = line.trimStart();
+          if (trimmed.startsWith("data:")) {
+            const json = trimmed.slice(5).trim();
+            return JSON.parse(json); // ✅ got it
+          }
+        }
+      }
+    }
+  } finally {
+    try {
+      await reader.cancel();
+    } catch {}
+  }
+}
packages/core/src/v3/schemas/api.ts (7)

32-36: Enforce externalRef format to match “starts with proj_”.

The schema only documents this requirement. Enforce it to prevent invalid data.

-  externalRef: z
-    .string()
-    .describe(
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9_-]+$/, "Must start with proj_")
+    .describe(
       "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
     ),

95-103: Type worker.engine with RunEngineVersion.

Avoid free-form strings; use the shared union for consistency.

 export const GetWorkerByTagResponse = z.object({
   worker: z.object({
     id: z.string(),
     version: z.string(),
-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),
     sdkVersion: z.string().nullish(),
     cliVersion: z.string().nullish(),
     tasks: z.array(GetWorkerTaskResponse),
   }),
 });

1162-1167: Enforce integer and default for pagination limit.

Add .int() and a default of 20 to match the description. Remove redundant .optional() so the default applies.

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .min(1, "Limit must be at least 1")
-  .max(100, "Limit must be less than 100")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1, "Limit must be at least 1")
+  .max(100, "Limit must be less than 100")
+  .default(20)
+  .describe("The number of deployments to return, defaults to 20 (max 100)");

1169-1176: Name the plain object shape distinctly from the Zod schema.

ApiDeploymentListParams is a plain object passed into z.object(); the name suggests a schema. Rename for clarity.

-export const ApiDeploymentListParams = {
+const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

1187-1206: Unify git metadata with GitMeta instead of record(any).

Use the shared GitMeta schema for stronger typing and consistency.

   deployedAt: z.coerce.date().optional(),
-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),
   error: DeploymentErrorData.optional(),

1210-1221: Use GitMeta in branch schema for consistency.

Same rationale as deployments.

       updatedAt: z.coerce.date(),
-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),
       isPaused: z.boolean(),

84-93: Export the inferred type for GetWorkerTaskResponse.

Consumers can’t import the nested task type currently.

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;
packages/cli-v3/src/mcp/tools/previewBranches.ts (1)

14-16: Dev-only guard blocks valid dev usage; remove or scope it to non-dev envs.

As written, --dev-only disables the tool entirely. Align with other tools by not blocking in dev.

-    if (ctx.options.devOnly) {
-      return respondWithError(`This MCP server is only available for the dev environment. `);
-    }
packages/cli-v3/src/mcp/context.ts (1)

78-79: Propagate preview branch to ApiClient so x-trigger-branch is set

The created ApiClient drops the branch, so downstream requests won’t include x-trigger-branch and preview behavior may be incorrect.

-    return new ApiClient(cliApiClient.apiURL, jwt.data.token);
+    return new ApiClient(cliApiClient.apiURL, jwt.data.token, options.branch);
packages/cli-v3/src/commands/install-mcp.ts (3)

1-1: Use the cross‑platform spinner wrapper and its zero‑arg API

Replace direct @clack/prompts spinner with our Windows-safe wrapper to avoid API mismatch and ensure consistent UX.

-import { confirm, intro, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
+import { confirm, intro, isCancel, log, multiselect, select } from "@clack/prompts";
+import { spinner } from "../utilities/windows.js";
-  const clientSpinner = spinner({ indicator: "dots" });
+  const clientSpinner = spinner();

Also applies to: 315-315


386-411: Harden path traversal when merging config (filter invalid segments; avoid silent break)

The loop breaks on falsy path segments, which silently truncates the path and can generate incorrect structures. Filter upfront and iterate the validated segments.

 function applyConfigToExistingConfig(
   existingConfig: any,
   pathComponents: string[],
   config: McpServerConfig
 ) {
-  const clonedConfig = structuredClone(existingConfig);
-
-  let currentValueAtPath = clonedConfig;
-
-  for (let i = 0; i < pathComponents.length; i++) {
-    const currentPathSegment = pathComponents[i];
-
-    if (!currentPathSegment) {
-      break;
-    }
-
-    if (i === pathComponents.length - 1) {
-      currentValueAtPath[currentPathSegment] = config;
-      break;
-    } else {
-      currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
-      currentValueAtPath = currentValueAtPath[currentPathSegment];
-    }
-  }
-
-  return clonedConfig;
+  const clonedConfig = structuredClone(existingConfig);
+  const segments = pathComponents.filter((s) => typeof s === "string" && s.length > 0);
+
+  if (segments.length === 0) return clonedConfig;
+
+  let cur = clonedConfig as any;
+  for (let i = 0; i < segments.length; i++) {
+    const seg = segments[i]!;
+    if (i === segments.length - 1) {
+      cur[seg] = config;
+    } else {
+      cur[seg] = cur[seg] || {};
+      cur = cur[seg];
+    }
+  }
+  return clonedConfig;
 }

437-439: Fix nested JSON path for AMP (avoid flat "amp.mcpServers" key)

Using a dotted key creates a flat property. Split the path into proper nested segments.

-    case "amp": {
-      return ["amp.mcpServers", "trigger"];
-    }
+    case "amp": {
+      return ["amp", "mcpServers", "trigger"];
+    }
packages/cli-v3/src/mcp/auth.ts (2)

120-152: Persist and reuse the actual API base URL resolved during auth; also rename token variable

Using opts.defaultApiUrl can break for staging/self-hosted. Persist apiClient.apiURL and reuse it for whoAmI and saved config. Rename indexResult → tokenResult for clarity.

-  const indexResult = await pRetry(
+  const tokenResult = await pRetry(
     () => getPersonalAccessToken(apiClient, authorizationCodeResult.authorizationCode),
     {
       //this means we're polling, same distance between each attempt
       factor: 1,
       retries: 60,
       minTimeout: 1000,
     }
   );
 
-  writeAuthConfigProfile(
-    { accessToken: indexResult.token, apiUrl: opts.defaultApiUrl },
-    options?.profile
-  );
-
-  const client = new CliApiClient(opts.defaultApiUrl, indexResult.token);
+  const apiUrlUsed = apiClient.apiURL;
+  writeAuthConfigProfile(
+    { accessToken: tokenResult.token, apiUrl: apiUrlUsed },
+    options?.profile
+  );
+
+  const client = new CliApiClient(apiUrlUsed, tokenResult.token);
@@
   return {
     ok: true as const,
     profile: options?.profile ?? "default",
     userId: userData.data.userId,
     email: userData.data.email,
     dashboardUrl: userData.data.dashboardUrl,
     auth: {
-      accessToken: indexResult.token,
-      apiUrl: opts.defaultApiUrl,
+      accessToken: tokenResult.token,
+      apiUrl: apiUrlUsed,
     },
   };

210-210: Pass previewBranch when constructing ApiClient with a public JWT

Ensures x-trigger-branch header is sent for branch-scoped requests.

-  return new ApiClient(auth.auth.apiUrl, jwt.data.token);
+  return new ApiClient(auth.auth.apiUrl, jwt.data.token, previewBranch);
packages/cli-v3/src/commands/init.ts (1)

156-156: Don’t terminate the flow after a successful MCP install (let init continue or prompt user)

Exiting here prevents initialization. Either remove the early return to continue, or prompt the user whether to continue.

-      return;
+      // Continue with CLI initialization after successful MCP install

If you prefer to explicitly prompt, I can provide a small follow-up patch to ask and branch accordingly.

🧹 Nitpick comments (15)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (2)

8-10: Tighten param validation to disallow empty projectRef.

Avoid accepting empty strings for projectRef.

Apply this diff:

-const ParamsSchema = z.object({
-  projectRef: z.string(),
-});
+const ParamsSchema = z.object({
+  projectRef: z.string().min(1, "projectRef is required"),
+});

27-38: Select only the fields you need from Prisma.

You only use project.id later; selecting just id reduces payload and parsing.

-  const project = await prisma.project.findFirst({
-    where: {
+  const project = await prisma.project.findFirst({
+    select: { id: true },
+    where: {
       externalRef: projectRef,
       organization: {
         members: {
           some: {
             userId: authenticationResult.userId,
           },
         },
       },
     },
   });
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (3)

442-452: Action options: consider parity with loader on 404 retry semantics.

findResource? is a great addition. To keep behavior consistent with the loader path, consider adding a shouldRetryNotFound?: boolean on the action options and emitting the x-should-retry header on 404s. This helps clients implement transient-retry logic consistently across loaders and actions.

Example (outside the shown lines):

type ApiKeyActionRouteBuilderOptions<...> = {
  // ...
  shouldRetryNotFound?: boolean;
};

Then include it in the action’s options destructure and 404 response headers (see comment below for the diff in the 404 block).


481-483: Tighten handler typing for resource when a finder is provided.

At runtime, you 404 when findResource exists and returns undefined, so handlers will never see an absent resource in that case. Consider reflecting this at the type level to prevent unnecessary undefined checks.

One approach is overloads on createActionApiRoute that specialize the handler signature:

// Overload when a resource is required via findResource
export function createActionApiRoute<
  TParamsSchema extends AnyZodSchema | undefined = undefined,
  TSearchParamsSchema extends AnyZodSchema | undefined = undefined,
  THeadersSchema extends AnyZodSchema | undefined = undefined,
  TBodySchema extends AnyZodSchema | undefined = undefined,
  TResource = never
>(
  options: ApiKeyActionRouteBuilderOptions<TParamsSchema, TSearchParamsSchema, THeadersSchema, TBodySchema, TResource> & {
    findResource: (...args: any[]) => Promise<TResource | undefined>;
  },
  handler: (args: Omit<Parameters<ApiKeyActionHandlerFunction<any, any, any, any, any>>[0], "resource"> & {
    resource: NonNullable<TResource>;
  }) => Promise<Response>
): { loader: LoaderFunction; action: ActionFunction };

// Fallback overload when no resource is provided
export function createActionApiRoute<...>(
  options: ApiKeyActionRouteBuilderOptions<...> & { findResource?: undefined },
  handler: ApiKeyActionHandlerFunction<...>
): { loader: LoaderFunction; action: ActionFunction };

This preserves back-compat while giving compile-time guarantees when findResource is used.

Also applies to: 500-500


709-710: Nit: align 404 payload with loader response.

The loader returns { error: "Not found" } while action uses "Resource not found". Consider standardizing the error string to avoid surprising client behavior.

Apply this minimal diff:

-          json({ error: "Resource not found" }, { status: 404 }),
+          json({ error: "Not found" }, { status: 404 }),

Optionally (if you add shouldRetryNotFound on action options), mirror the loader’s retry header:

json(
  { error: "Not found" },
  { status: 404, headers: { "x-should-retry": shouldRetryNotFound ? "true" : "false" } }
)
packages/cli-v3/src/mcp/mintlifyClient.ts (3)

4-12: Handle non-2xx responses and add a timeout/abort to the fetch.

Right now, errors from the server (4xx/5xx) will flow into the parsers and produce confusing exceptions. Add an AbortController-based timeout and fail fast on !response.ok with the server's error text for better DX.

-  const response = await fetch("https://trigger.dev/docs/mcp", {
-    method: "POST",
-    headers: {
-      "Content-Type": "application/json",
-      Accept: "application/json, text/event-stream",
-      "MCP-Protocol-Version": "2025-06-18",
-    },
-    body: JSON.stringify(body),
-  });
+  const ac = new AbortController();
+  const timeout = setTimeout(() => ac.abort(new Error("MCP request timed out after 30s")), 30_000);
+  let response: Response;
+  try {
+    response = await fetch("https://trigger.dev/docs/mcp", {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        Accept: "application/json, text/event-stream",
+        "MCP-Protocol-Version": "2025-06-18",
+      },
+      body: JSON.stringify(body),
+      signal: ac.signal,
+    });
+  } finally {
+    clearTimeout(timeout);
+  }
+
+  if (!response.ok) {
+    const text = await response.text().catch(() => "");
+    throw new Error(
+      `MCP call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`
+    );
+  }

18-24: Content-Type check should be case-insensitive and robust to parameters.

Be defensive against charset suffixes and case; also consider handling 204/empty bodies gracefully.

-  if (response.headers.get("content-type")?.includes("text/event-stream")) {
+  const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
+  if (contentType.includes("text/event-stream")) {
     return parseSSEResponse(response);
   } else {
     return parseJSONResponse(response);
   }

63-73: Avoid constant JSON-RPC id; generate a per-call id.

Using a fixed id: 1 can cause confusion with multiplexed calls. Generate a simple unique id by default.

-function callToolBody(tool: string, args: Record<string, unknown>) {
+function callToolBody(
+  tool: string,
+  args: Record<string, unknown>,
+  id: number | string = Date.now()
+) {
   return {
     jsonrpc: "2.0",
-    id: 1,
+    id,
     method: "tools/call",
     params: {
       name: tool,
       arguments: args,
     },
   };
}
apps/webapp/app/routes/api.v1.orgs.ts (1)

25-27: Return 200 with an empty array for list endpoints.

Prisma’s findMany returns [] (truthy). A 404 on “no orgs” is atypical for list endpoints and inconsistent with many REST practices.

-  if (!orgs) {
-    return json({ error: "Orgs not found" }, { status: 404 });
-  }
+  // Intentionally return an empty array when there are no orgs
packages/cli-v3/src/mcp/context.ts (1)

116-117: Improve error context for missing project ref

The thrown error is generic. Including the resolved cwd helps users debug monorepo/relative path cases.

-    throw new Error("No project ref found. Please provide a projectRef.");
+    throw new Error(
+      `No project ref found. Please provide a projectRef or ensure trigger.config is present in ${projectDir.cwd}.`
+    );
packages/cli-v3/src/commands/install-mcp.ts (1)

356-381: Add default/fallback for unsupported config extensions

If a client config path has an unexpected extension, this silently no-ops. Prefer a safe JSON fallback and emit a warning.

   switch (extension) {
     case ".json": {
       let existingConfig = await safeReadJSONCFile(fullPath);
@@
       await writeTomlFile(fullPath, newConfig);
       break;
     }
+    default: {
+      log.warn(
+        `Unrecognized config extension "${extension}" at ${fullPath}. Falling back to JSON semantics.`
+      );
+      let existingConfig = await safeReadJSONCFile(fullPath);
+      if (!existingConfig) existingConfig = {};
+      const newConfig = applyConfigToExistingConfig(existingConfig, pathComponents, config);
+      await writeJSONFile(fullPath, newConfig, true);
+      break;
+    }
   }
packages/cli-v3/src/commands/init.ts (2)

498-504: Prefer type alias over interface (repo guideline)

Guidelines favor types over interfaces for TS. A class can still implement an object type alias.

-export interface InstallPackagesOutputter {
-  startSDK: () => void;
-  installedSDK: () => void;
-  startBuild: () => void;
-  installedBuild: () => void;
-  stoppedWithError: () => void;
-}
+export type InstallPackagesOutputter = {
+  startSDK: () => void;
+  installedSDK: () => void;
+  startBuild: () => void;
+  installedBuild: () => void;
+  stoppedWithError: () => void;
+};

326-329: Handle absolute paths portably when creating the trigger directory

Stripping a leading “/” doesn’t handle Windows absolute paths. Use path.isAbsolute and convert to a project-relative path (or reject absolute).

-      // Ensure that the path is always relative by stripping leading '/' if present
-      const relativeLocation = location.replace(/^\//, "");
+      // Ensure the path is project-relative across platforms
+      const relativeLocation = isAbsolute(location) ? relative(process.cwd(), location) : location.replace(/^\//, "");

Add this import at the top of the file outside the diffed range:

import { isAbsolute } from "node:path";

packages/cli-v3/src/mcp/schemas.ts (2)

8-14: Validate project refs when provided

Since ProjectRefSchema is optional, only validate when present. Enforce the proj_ prefix to catch typos early.

-export const ProjectRefSchema = z
-  .string()
+export const ProjectRefSchema = z
+  .string()
+  .regex(/^proj_[A-Za-z0-9]+$/, "Project ref must start with 'proj_'")
   .describe(
     "The trigger.dev project ref, starts with proj_. We will attempt to automatically detect the project ref if running inside a directory that includes a trigger.config.ts file, or if you pass the --project-ref option to the MCP server."
   )
   .optional();

43-59: Require branch when environment is preview

Prevents invalid preview requests by asserting branch presence when environment === "preview".

-export const CommonProjectsInput = z.object({
-  projectRef: ProjectRefSchema,
-  configPath: z
-    .string()
-    .describe(
-      "The path to the trigger.config.ts file. Only used when the trigger.config.ts file is not at the root dir (like in a monorepo setup). If not provided, we will try to find the config file in the current working directory"
-    )
-    .optional(),
-  environment: z
-    .enum(["dev", "staging", "prod", "preview"])
-    .describe("The environment to get tasks for")
-    .default("dev"),
-  branch: z
-    .string()
-    .describe("The branch to get tasks for, only used for preview environments")
-    .optional(),
-});
+export const CommonProjectsInput = z
+  .object({
+    projectRef: ProjectRefSchema,
+    configPath: z
+      .string()
+      .describe(
+        "The path to the trigger.config.ts file. Only used when the trigger.config.ts file is not at the root dir (like in a monorepo setup). If not provided, we will try to find the config file in the current working directory"
+      )
+      .optional(),
+    environment: z
+      .enum(["dev", "staging", "prod", "preview"])
+      .describe("The environment to get tasks for")
+      .default("dev"),
+    branch: z
+      .string()
+      .describe("The branch to get tasks for, only used for preview environments")
+      .optional(),
+  })
+  .superRefine((val, ctx) => {
+    if (val.environment === "preview" && !val.branch) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        message: "branch is required when environment is 'preview'",
+        path: ["branch"],
+      });
+    }
+  });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5313b53 and 96609ec.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (52)
  • .cursor/mcp.json (1 hunks)
  • .gitignore (1 hunks)
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (5 hunks)
  • apps/webapp/app/routes/api.v1.deployments.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.orgs.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts (2 hunks)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.projects.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts (1 hunks)
  • apps/webapp/app/routes/api.v1.runs.ts (1 hunks)
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts (1 hunks)
  • apps/webapp/app/services/authorization.server.ts (1 hunks)
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4 hunks)
  • packages/cli-v3/install-mcp.sh (1 hunks)
  • packages/cli-v3/package.json (2 hunks)
  • packages/cli-v3/src/apiClient.ts (3 hunks)
  • packages/cli-v3/src/cli/common.ts (1 hunks)
  • packages/cli-v3/src/cli/index.ts (2 hunks)
  • packages/cli-v3/src/commands/deploy.ts (1 hunks)
  • packages/cli-v3/src/commands/dev.ts (2 hunks)
  • packages/cli-v3/src/commands/init.ts (9 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/login.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/dev/devOutput.ts (2 hunks)
  • packages/cli-v3/src/mcp/auth.ts (1 hunks)
  • packages/cli-v3/src/mcp/capabilities.ts (1 hunks)
  • packages/cli-v3/src/mcp/config.ts (1 hunks)
  • packages/cli-v3/src/mcp/context.ts (1 hunks)
  • packages/cli-v3/src/mcp/logger.ts (1 hunks)
  • packages/cli-v3/src/mcp/mintlifyClient.ts (1 hunks)
  • packages/cli-v3/src/mcp/schemas.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/deploys.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/docs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/orgs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/previewBranches.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/runs.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/tasks.ts (1 hunks)
  • packages/cli-v3/src/mcp/types.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
  • packages/cli-v3/src/utilities/configFiles.ts (3 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • packages/core/src/v3/apiClient/index.ts (3 hunks)
  • packages/core/src/v3/isomorphic/dates.ts (1 hunks)
  • packages/core/src/v3/isomorphic/index.ts (1 hunks)
  • packages/core/src/v3/schemas/api.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/cli-v3/src/dev/devOutput.ts
🚧 Files skipped from review as they are similar to previous changes (39)
  • apps/webapp/app/routes/api.v1.runs.ts
  • packages/cli-v3/src/commands/deploy.ts
  • apps/webapp/app/routes/api.v1.projects.ts
  • .gitignore
  • .cursor/mcp.json
  • packages/cli-v3/src/mcp/config.ts
  • packages/cli-v3/src/mcp/tools/runs.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts
  • packages/cli-v3/src/cli/common.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.branches.ts
  • packages/cli-v3/src/mcp/capabilities.ts
  • packages/cli-v3/src/mcp/tools.ts
  • packages/core/src/v3/isomorphic/index.ts
  • apps/webapp/app/routes/api.v1.deployments.ts
  • packages/cli-v3/src/mcp/logger.ts
  • packages/cli-v3/src/commands/login.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • packages/cli-v3/src/mcp/tools/tasks.ts
  • packages/cli-v3/src/mcp/utils.ts
  • apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts
  • apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts
  • packages/cli-v3/src/utilities/configFiles.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.jwt.ts
  • packages/cli-v3/src/utilities/fileSystem.ts
  • packages/cli-v3/src/mcp/tools/deploys.ts
  • apps/webapp/app/routes/api.v1.runs.$runId.trace.ts
  • packages/cli-v3/src/commands/mcp.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/package.json
  • packages/cli-v3/src/mcp/types.ts
  • apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx
  • apps/webapp/app/services/authorization.server.ts
  • packages/cli-v3/src/mcp/tools/docs.ts
  • packages/core/src/v3/apiClient/index.ts
  • packages/cli-v3/install-mcp.sh
  • packages/core/src/v3/isomorphic/dates.ts
  • packages/cli-v3/src/commands/dev.ts
  • packages/cli-v3/src/mcp/tools/orgs.ts
  • packages/cli-v3/src/apiClient.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/tools/previewBranches.ts
  • packages/cli-v3/src/mcp/mintlifyClient.ts
  • packages/cli-v3/src/mcp/context.ts
  • apps/webapp/app/routes/api.v1.orgs.ts
  • packages/cli-v3/src/commands/init.ts
  • packages/cli-v3/src/mcp/auth.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
  • packages/cli-v3/src/commands/install-mcp.ts
  • packages/cli-v3/src/cli/index.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/core/src/v3/schemas/api.ts
  • packages/cli-v3/src/mcp/schemas.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/api.v1.orgs.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
  • packages/core/src/v3/schemas/api.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.orgs.ts
  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
  • apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts
apps/webapp/app/services/**/*.server.ts

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

For testable services, separate service logic and configuration, as exemplified by realtimeClient.server.ts (service) and realtimeClientGlobal.server.ts (configuration).

Files:

  • apps/webapp/app/services/routeBuilders/apiBuilder.server.ts
🧠 Learnings (4)
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities

Applied to files:

  • packages/cli-v3/src/commands/init.ts
📚 Learning: 2025-08-14T12:13:20.408Z
Learnt from: myftija
PR: triggerdotdev/trigger.dev#2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.408Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • packages/cli-v3/src/mcp/schemas.ts
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project

Applied to files:

  • packages/cli-v3/src/mcp/schemas.ts
🧬 Code Graph Analysis (9)
packages/cli-v3/src/mcp/tools/previewBranches.ts (3)
packages/cli-v3/src/mcp/config.ts (1)
  • toolsMetadata (12-91)
packages/cli-v3/src/mcp/schemas.ts (2)
  • ListPreviewBranchesInput (203-211)
  • ListPreviewBranchesInput (213-213)
packages/cli-v3/src/mcp/utils.ts (2)
  • toolHandler (40-53)
  • respondWithError (6-16)
packages/cli-v3/src/mcp/context.ts (6)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
packages/cli-v3/src/mcp/auth.ts (1)
  • mcpAuth (24-153)
packages/cli-v3/src/apiClient.ts (1)
  • CliApiClient (55-763)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/mcp/capabilities.ts (3)
  • hasRootsCapability (3-11)
  • hasSamplingCapability (13-21)
  • hasElicitationCapability (23-31)
apps/webapp/app/routes/api.v1.orgs.ts (5)
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)
  • loader (19-68)
apps/webapp/app/routes/api.v1.projects.ts (1)
  • loader (8-54)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/core/src/v3/schemas/api.ts (2)
  • GetOrgsResponseBody (54-61)
  • GetOrgsResponseBody (63-63)
packages/cli-v3/src/commands/init.ts (8)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readConfigHasSeenMCPInstallPrompt (104-109)
  • writeConfigHasSeenMCPInstallPrompt (111-118)
packages/core/src/v3/utils/structuredLogger.ts (1)
  • log (38-42)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (184-248)
packages/cli-v3/src/cli/common.ts (1)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
packages/trigger-sdk/src/v3/index.ts (1)
  • LogLevel (40-40)
packages/core/src/v3/index.ts (1)
  • LogLevel (40-40)
packages/core/src/v3/apiClient/stream.ts (1)
  • e (216-222)
packages/cli-v3/src/mcp/auth.ts (8)
packages/cli-v3/src/mcp/context.ts (1)
  • McpContext (24-187)
packages/cli-v3/src/utilities/session.ts (2)
  • LoginResult (19-28)
  • LoginResultOk (7-17)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/utilities/isPersonalAccessToken.ts (1)
  • NotPersonalAccessTokenError (7-12)
packages/cli-v3/src/apiClient.ts (3)
  • CliApiClient (55-763)
  • createAuthorizationCode (69-77)
  • getPersonalAccessToken (79-86)
packages/cli-v3/src/utilities/configFiles.ts (2)
  • readAuthConfigProfile (92-102)
  • writeAuthConfigProfile (81-90)
packages/cli-v3/src/commands/login.ts (1)
  • getPersonalAccessToken (349-378)
packages/core/src/v3/apiClient/index.ts (1)
  • ApiClient (143-1107)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)
apps/webapp/app/services/apiAuth.server.ts (1)
  • ApiAuthenticationResultSuccess (44-54)
packages/cli-v3/src/commands/install-mcp.ts (5)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • writeConfigHasSeenMCPInstallPrompt (111-118)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (5)
  • expandTilde (54-68)
  • safeReadJSONCFile (139-147)
  • writeJSONFile (90-92)
  • safeReadTomlFile (125-133)
  • writeTomlFile (135-137)
packages/cli-v3/src/cli/index.ts (2)
packages/cli-v3/src/commands/mcp.ts (1)
  • configureMcpCommand (26-42)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • configureInstallMcpCommand (122-157)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (4)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-147)
apps/webapp/app/presenters/v3/DevPresence.server.ts (1)
  • devPresence (29-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
apps/webapp/app/routes/api.v1.projects.$projectRef.dev-status.ts (1)

1-1: Good call using '@remix-run/node' for a server-only route.

This aligns with our standardization for Remix loaders on Node.

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (3)

433-435: Solid generic extension for action options.

Adding TBodySchema and TResource to ApiKeyActionRouteBuilderOptions aligns the action options with the handler’s needs and keeps defaults sensible. LGTM.


507-516: Generic plumbing for TResource and TBodySchema in createActionApiRoute looks correct.

Type parameters are threaded through both the options and handler consistently. No issues spotted.

Also applies to: 521-523


721-721: Handler invocation now receives the resolved resource.

Passing resource through maintains the typed flow you introduced. LGTM.

packages/cli-v3/src/cli/index.ts (1)

39-40: LGTM: MCP commands are correctly wired into the CLI.

Wiring looks consistent with other commands; no issues spotted.

packages/cli-v3/src/mcp/context.ts (1)

123-126: LGTM: cross-platform absolute path detection

Good fix to use path.isAbsolute for Windows-safe cwd resolution, addressing prior startsWith("/") issues.

Also applies to: 137-137

packages/cli-v3/src/commands/init.ts (1)

116-119: LGTM: enforce --project-ref when using --yes

Solid guardrail that aligns with non-interactive expectations.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (1)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)

17-56: Pass x-trigger-branch through to getEnvironmentFromEnv for preview requests

The loader never forwards the branch header, so preview requests to this route will always error with “Preview environment requires a branch…”. Read the header and pass it along.

 export async function loader({ request, params }: LoaderFunctionArgs) {
   logger.info("projects get env", { url: request.url });
 
   const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
@@
   const parsedParams = ParamsSchema.safeParse(params);
@@
-  const { projectRef, env } = parsedParams.data;
+  const { projectRef, env } = parsedParams.data;
+  const branch = request.headers.get("x-trigger-branch") ?? undefined;
@@
-  const envResult = await getEnvironmentFromEnv({
+  const envResult = await getEnvironmentFromEnv({
     projectId: project.id,
     userId: authenticationResult.userId,
-    env,
+    env,
+    branch,
   });
♻️ Duplicate comments (25)
packages/cli-v3/src/commands/update.ts (1)

4-4: Fix Windows path bug in the package.json test filter by using path.join() for dist markers

Join is now imported but the filter still compares POSIX strings ("dist/commonjs", "dist/esm"). On Windows those checks miss backslashes, causing the resolver to read the type-marker package.json instead of the root one.

Apply this diff:

   const { packageJson } = await getPackageJson(dirname(resolvedPath), {
     test: (filePath) => {
-      // We need to skip any type-marker files
-      if (filePath.includes("dist/commonjs")) {
-        return false;
-      }
-
-      if (filePath.includes("dist/esm")) {
-        return false;
-      }
-
-      return true;
+      // We need to skip any type-marker files (cross-platform)
+      const markers = [join("dist", "commonjs"), join("dist", "esm")];
+      if (markers.some((m) => filePath.includes(m))) {
+        return false;
+      }
+      return true;
     },
   });

Also applies to: 342-355

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (1)

702-705: Resolved per-request resource in actions — thanks for adding this.

This addresses the prior review concern about undefined resources reaching handlers in action routes.

packages/cli-v3/install-mcp.sh (2)

119-167: Consolidate duplicated Node JSON-manipulation into a reusable helper.

The inline Node blocks are nearly identical across all install functions. Centralize into one Bash helper (that runs a single node -e with parameters) to cut maintenance and reduce drift.

Example helper to add near the top (after line 101), then replace each inline node -e block with a one-liner call:

# Helper: update an MCP-capable JSON config file
update_json_config() {
  local config_path="$1"  # absolute path to JSON file
  local mode="$2"         # one of: mcpServers | servers | crush
  node -e "
    const fs = require('fs');
    const configPath = process.env.CONFIG_PATH;
    const nodePath = process.env.NODE_PATH_BIN;
    const cliPath = process.env.CLI_PATH_FILE;
    const logFile = process.env.MCP_LOG_FILE_PATH;
    const mode = process.env.MODE;

    try {
      let config;
      try {
        const raw = fs.readFileSync(configPath, 'utf8');
        config = JSON.parse(raw);
      } catch {
        console.log('📝 Creating new configuration structure...');
        config = {};
      }

      // Ensure structures
      if (mode === 'servers') {
        config.servers ??= {};
        config.servers['trigger'] = {
          command: nodePath,
          args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', 'http://localhost:3030']
        };
      } else if (mode === 'crush') {
        config['$schema'] ??= 'https://charm.land/crush.json';
        config.mcp ??= {};
        config.mcp['trigger'] = {
          type: 'stdio',
          command: nodePath,
          args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', 'http://localhost:3030']
        };
      } else {
        config.mcpServers ??= {};
        config.mcpServers['trigger'] = {
          command: nodePath,
          args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', 'http://localhost:3030']
        };
      }

      // Write with formatting
      fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
      console.log('✅ Successfully updated', configPath);
    } catch (err) {
      console.error('❌ Error updating configuration:', err.message);
      process.exit(1);
    }
  " \
  CONFIG_PATH="$config_path" \
  NODE_PATH_BIN="$NODE_PATH" \
  CLI_PATH_FILE="$CLI_PATH" \
  MCP_LOG_FILE_PATH="$MCP_LOG_FILE" \
  MODE="$mode"
}

Then, for example in install_claude replace the entire inline Node block with:

update_json_config "$CLAUDE_CONFIG" "mcpServers"
  • Claude Desktop: "mcpServers"
  • Cursor: "mcpServers"
  • VS Code: "servers"
  • Crush: "crush"
  • Windsurf: "mcpServers"

Reply if you want me to push a complete patch transforming all six blocks.

Also applies to: 194-242, 269-317, 344-392, 419-471, 498-546


38-54: Guard against missing value for -t/--target (prevents bad shifts and empty TARGET).

If the user passes -t/--target without a value, the script does shift 2 and continues with an empty or flag-looking $2. Add a validation guard before assigning.

-        -t|--target)
-            TARGET="$2"
-            shift 2
-            ;;
+        -t|--target)
+            if [[ -z "${2:-}" ]] || [[ "${2}" == -* ]]; then
+                echo "❌ Missing value for $1"
+                echo "Use -h or --help for usage information"
+                exit 1
+            fi
+            TARGET="$2"
+            shift 2
+            ;;
packages/cli-v3/src/mcp/mintlifyClient.ts (1)

32-62: SSE reader: cancel the stream, support CRLF and multi-line data, and avoid dangling/unreachable code

  • Cancel the reader on all exit paths to free resources.
  • Handle CRLF delimiters and multi-line data: payloads (SSE may split large JSON across multiple data lines).
  • Process any buffered data when done === true before erroring.
  • Remove the unreachable throw after the infinite loop.
 // Get the first data: event and return the parsed JSON of the event
 async function parseSSEResponse(response: Response) {
   const reader = response.body?.getReader();
   const decoder = new TextDecoder();
 
   if (!reader) {
     throw new Error("No reader found");
   }
 
   let buffer = "";
 
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) throw new Error("SSE stream closed before data arrived");
-
-    buffer += decoder.decode(value, { stream: true });
-    const events = buffer.split("\n\n"); // SSE delimiter
-    buffer = events.pop()!; // keep incomplete
-
-    for (const evt of events) {
-      for (const line of evt.split("\n")) {
-        if (line.startsWith("data:")) {
-          const json = line.slice(5).trim();
-          return JSON.parse(json); // ✅ got it
-        }
-      }
-    }
-  }
-
-  throw new Error("No data: event found");
+  try {
+    while (true) {
+      const { value, done } = await reader.read();
+
+      if (done) {
+        // Process any remaining buffered events before failing
+        if (buffer.length > 0) {
+          for (const evt of buffer.split(/\r?\n\r?\n/g)) {
+            const dataLines = evt
+              .split(/\r?\n/g)
+              .filter((line) => line.startsWith("data:"))
+              .map((line) => line.slice(5).trim());
+            const payload = dataLines.join("\n");
+            if (payload) {
+              return JSON.parse(payload);
+            }
+          }
+        }
+        throw new Error("SSE stream closed before data arrived");
+      }
+
+      buffer += decoder.decode(value, { stream: true });
+      const events = buffer.split(/\r?\n\r?\n/g); // SSE delimiter
+      buffer = events.pop() ?? ""; // keep incomplete
+
+      for (const evt of events) {
+        const dataLines = evt
+          .split(/\r?\n/g)
+          .filter((line) => line.startsWith("data:"))
+          .map((line) => line.slice(5).trim());
+        const payload = dataLines.join("\n");
+        if (payload) {
+          return JSON.parse(payload); // ✅ got it
+        }
+      }
+    }
+  } finally {
+    // Ensure the stream is cancelled/closed cleanly
+    try {
+      await reader.cancel();
+    } catch {}
+  }
 }
packages/cli-v3/src/mcp/tools/docs.ts (1)

1-1: Fix Zod import (default import is invalid in ESM/TS setups)

Use the named import.

-import z from "zod";
+import { z } from "zod";
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1)

120-141: Validate the response against the Zod schema before returning

You’re importing the schema but not parsing. Validating catches shape drift and ensures only well-formed payloads leave the route.

-  return json(response);
+  return json(GetWorkerByTagResponse.parse(response));
packages/cli-v3/src/commands/mcp.ts (1)

45-64: Interactive mode returns early and never starts the server

After install, the command returns, so “mcp” doesn’t actually run the server in interactive mode. On failure, the error is not propagated.

-    if (installError) {
-      outro(`Failed to install MCP server: ${installError.message}`);
-      return;
-    }
-
-    return;
+    if (installError) {
+      outro(`Failed to install MCP server: ${installError.message}`);
+      throw installError;
+    }
+    // Continue to server startup below
packages/cli-v3/src/mcp/tools/deploys.ts (4)

122-126: Dev-only guard for list_deploys should allow dev environment (duplicate of prior review)

Match the pattern used elsewhere: only block when trying to access non-dev environments.

-    if (ctx.options.devOnly) {
+    if (ctx.options.devOnly && input.environment !== "dev") {
       return respondWithError(
         `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
       );
     }

72-80: Child process environment drops PATH and other vars (duplicate of prior review)

Only setting TRIGGER_MCP_SERVER/CI loses PATH and breaks command resolution. Merge parent env.

-    const deployProcess = x(nodePath, [cliPath, ...args], {
-      nodeOptions: {
-        cwd: cwd.cwd,
-        env: {
-          TRIGGER_MCP_SERVER: "1",
-          CI: "true",
-        },
-      },
-    });
+    const deployProcess = x(nodePath, [cliPath, ...args], {
+      nodeOptions: {
+        cwd: cwd.cwd,
+        env: {
+          ...process.env,
+          TRIGGER_MCP_SERVER: "1",
+          CI: "true",
+        },
+      },
+    });

148-179: resolveCLIExec returns current Node argv instead of invoking npx (duplicate of prior review)

Returning process.argv[0]/[1] won’t launch npx/trigger.dev. Use explicit npx invocations.

   if (!sdkVersion) {
     context.logger?.log("resolve_cli_exec no sdk version found", { cwd });
 
-    return [process.argv[0] ?? "npx", process.argv[1] ?? "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
   }
@@
   if (sdkVersion === VERSION) {
     context.logger?.log("resolve_cli_exec sdk version is the same as the current version", {
       sdkVersion,
     });
 
-    if (typeof process.argv[0] === "string" && typeof process.argv[1] === "string") {
-      return [process.argv[0], process.argv[1]];
-    }
-
-    return ["npx", "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
   }
 
   return ["npx", `trigger.dev@${sdkVersion}`];

21-25: Dev-only guard for deploy wrongly blocks valid dev deployments

Align with other tools: only block when devOnly is set and the requested environment isn’t dev.

-    if (ctx.options.devOnly) {
-      return respondWithError(
-        `This MCP server is only available for the dev environment. The deploy command is not allowed with the --dev-only flag.`
-      );
-    }
+    if (ctx.options.devOnly && input.environment !== "dev") {
+      return respondWithError(
+        `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
+      );
+    }
packages/cli-v3/src/mcp/context.ts (1)

78-79: Propagate preview branch to ApiClient (duplicate of prior review)

Without passing branch here, x-trigger-branch won’t be sent and preview operations can be misrouted.

-    return new ApiClient(cliApiClient.apiURL, jwt.data.token);
+    return new ApiClient(cliApiClient.apiURL, jwt.data.token, options.branch);
packages/cli-v3/src/commands/install-mcp.ts (4)

386-411: Sanitize and validate path components when applying config (duplicate of prior review)

Filtering out empty segments avoids silent structural errors and makes the loop simpler.

 function applyConfigToExistingConfig(
   existingConfig: any,
   pathComponents: string[],
   config: McpServerConfig
 ) {
-  const clonedConfig = structuredClone(existingConfig);
-
-  let currentValueAtPath = clonedConfig;
-
-  for (let i = 0; i < pathComponents.length; i++) {
-    const currentPathSegment = pathComponents[i];
-
-    if (!currentPathSegment) {
-      break;
-    }
-
-    if (i === pathComponents.length - 1) {
-      currentValueAtPath[currentPathSegment] = config;
-      break;
-    } else {
-      currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
-      currentValueAtPath = currentValueAtPath[currentPathSegment];
-    }
-  }
-
-  return clonedConfig;
+  const validPathComponents = pathComponents.filter(Boolean);
+  const clonedConfig = structuredClone(existingConfig);
+  let currentValueAtPath = clonedConfig;
+
+  for (let i = 0; i < validPathComponents.length; i++) {
+    const currentPathSegment = validPathComponents[i]!;
+    if (i === validPathComponents.length - 1) {
+      currentValueAtPath[currentPathSegment] = config;
+    } else {
+      currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
+      currentValueAtPath = currentValueAtPath[currentPathSegment];
+    }
+  }
+
+  return clonedConfig;
 }

1-1: Use the cross-platform spinner wrapper and its zero-arg API (duplicate of prior review)

Import spinner from our windows wrapper to ensure consistent UX and correct API.

-import { confirm, intro, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
+import { confirm, intro, isCancel, log, multiselect, select } from "@clack/prompts";
+import { spinner } from "../utilities/windows.js";

315-325: Adjust spinner construction to wrapper’s API (duplicate of prior review)

The wrapper exposes a zero-arg factory.

-  const clientSpinner = spinner({ indicator: "dots" });
+  const clientSpinner = spinner();

437-439: Fix AMP nested JSON path (duplicate of prior review)

Using a dot creates a flat key "amp.mcpServers". Split into segments to build a nested structure.

-    case "amp": {
-      return ["amp.mcpServers", "trigger"];
-    }
+    case "amp": {
+      return ["amp", "mcpServers", "trigger"];
+    }
packages/cli-v3/src/mcp/tools/orgs.ts (1)

144-197: Initialize flow now respects provided projectRef — previous issue resolved.

You correctly short-circuit creation when input.projectRef is provided. This addresses the earlier concern that the tool always created a project.

packages/core/src/v3/schemas/api.ts (6)

32-36: Enforce externalRef format (must start with proj_).

The description documents the constraint but it isn’t validated. Add a regex to guarantee the contract.

Apply this diff:

-  externalRef: z
-    .string()
-    .describe(
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9]+$/, "Must start with proj_")
+    .describe(
       "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
     ),

84-93: Export the GetWorkerTaskResponse TypeScript type.

This schema is exported but its inferred TS type isn’t, which hampers consumers using the nested type.

Apply this diff:

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
 
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

95-102: Use RunEngineVersion for worker.engine.

Strengthen typing by reusing the existing RunEngineVersion union instead of a free-form string.

Apply this diff:

 export const GetWorkerByTagResponse = z.object({
   worker: z.object({
     id: z.string(),
     version: z.string(),
-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),
     sdkVersion: z.string().nullish(),
     cliVersion: z.string().nullish(),
     tasks: z.array(GetWorkerTaskResponse),
   }),

1165-1171: Pagination limit: enforce integer and provide a default of 20.

Match the description by making it an int, clamping to [1,100], and defaulting to 20.

Apply this diff:

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .min(1, "Limit must be at least 1")
-  .max(100, "Limit must be less than 100")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1, "Limit must be at least 1")
+  .max(100, "Limit must be less than 100")
+  .default(20)
+  .describe("The number of deployments to return, defaults to 20 (max 100)");

1207-1207: Unify git metadata with GitMeta (avoid loose records).

Use the existing GitMeta schema for stronger, consistent typing across deployments and branches.

Apply this diff:

-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),

And:

-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),

Also applies to: 1220-1220


1172-1179: Clarify naming: distinguish plain shape vs Zod schema for deployment list params.

ApiDeploymentListParams is a plain object shape, but ApiDeploymentListOptions is the Zod schema. Renaming the shape improves readability.

Proposed change:

- export const ApiDeploymentListParams = {
+ const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

If you adopt this, update imports that rely on ApiDeploymentListParams as a shape. To locate usages:

#!/bin/bash
# Find references to ApiDeploymentListParams to assess impact of renaming.
rg -n -C2 '\bApiDeploymentListParams\b'
packages/cli-v3/src/mcp/schemas.ts (1)

26-33: Schema/handler mismatch previously flagged is now addressed.

projectRef is optional here, enabling the creation flow in initialize_project as intended.

🧹 Nitpick comments (50)
.gitignore (1)

66-67: Broaden MCP log ignore pattern
To ensure all .mcp.log files—rotated variants and those emitted by subpackages—are properly ignored, replace the flat entry with a recursive pattern that catches nested and rotated logs.

Affected references found:

  • packages/cli-v3/package.json (inspector --log-file .mcp.log)
  • packages/cli-v3/install-mcp.sh (defines MCP_LOG_FILE="$SCRIPT_DIR/.mcp.log" and passes --log-file in multiple server configs)

Apply this diff to .gitignore:

 .claude
- .mcp.log
+# MCP logs (nested packages and rotated files)
+**/.mcp.log*
apps/webapp/app/routes/projects.$projectRef.ts (4)

6-8: Add a minimal constraint to the param schema

A non-empty guard helps avoid accidental empty strings and improves error messages.

Apply this diff:

 const ParamsSchema = z.object({
-  projectRef: z.string(),
+  projectRef: z.string().min(1, "projectRef is required"),
 });

15-29: Reduce payload and align with soft-delete conventions

  • You only need slugs for the redirect. Prefer select over include to avoid fetching entire records.
  • Optional: If your app uses soft-deletes (e.g., deletedAt), consider filtering them here for consistency with other presenters.

Apply this diff to minimize the query payload:

   const project = await prisma.project.findFirst({
     where: {
       externalRef: validatedParams.projectRef,
       organization: {
         members: {
           some: {
             userId,
           },
         },
       },
     },
-    include: {
-      organization: true,
-    },
+    select: {
+      slug: true,
+      organization: { select: { slug: true } },
+    },
   });

If relevant to your data model, you may also want to add soft-delete guards (field names may differ, so adjust accordingly):

where: {
  externalRef: validatedParams.projectRef,
  // deletedAt: null,
  organization: {
    // deletedAt: null,
    members: { some: { userId /*, deletedAt: null */ } },
  },
},

31-33: Prefer throwing 404 to integrate with Remix CatchBoundary

Throwing the Response plays nicer with route error boundaries and keeps patterns consistent across loaders.

Apply this diff:

-  if (!project) {
-    return new Response("Not found", { status: 404 });
-  }
+  if (!project) {
+    throw new Response("Not found", { status: 404 });
+  }

35-35: Update the comment to match the actual redirect

Path doesn’t include a runs segment; the comment implies it does. Keep comments accurate to avoid confusion.

Apply this diff:

-  // Redirect to the project's runs page
+  // Redirect to the project's page within its organization
apps/webapp/app/routes/projects.v3.$projectRef.ts (1)

8-12: Encode the param and preserve the query string; also drop unnecessary async.

This avoids malformed URLs when projectRef contains reserved characters and keeps any incoming query params intact. The loader doesn’t await, so it can be synchronous.

-export async function loader({ params, request }: LoaderFunctionArgs) {
+export function loader({ params, request }: LoaderFunctionArgs) {
   const validatedParams = ParamsSchema.parse(params);
 
-  return redirect(`/projects/${validatedParams.projectRef}`);
+  const { search } = new URL(request.url);
+  return redirect(`/projects/${encodeURIComponent(validatedParams.projectRef)}${search}`);
 }

Note: If this mapping is intended to be permanent, consider redirect(..., { status: 308 }) to signal a permanent redirect.

apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (4)

442-452: Consider passing parsed body into findResource for action routes.

Some action routes need request body context to resolve the resource (e.g., IDs inside payload). Since you already parse the body earlier, expose it to findResource.

Apply this diff to the signature:

   findResource?: (
     params: TParamsSchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TParamsSchema>
       : undefined,
     authentication: ApiAuthenticationResultSuccess,
     searchParams: TSearchParamsSchema extends
       | z.ZodFirstPartySchemaTypes
       | z.ZodDiscriminatedUnion<any, any>
       ? z.infer<TSearchParamsSchema>
-      : undefined
+      : undefined,
+    body: TBodySchema extends z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>
+      ? z.infer<TBodySchema>
+      : undefined
   ) => Promise<TResource | undefined>;

And update the call site (see Lines 702-705) to pass parsedBody as the fourth argument.


500-501: Optionally tighten the handler resource type.

You can make the handler’s resource slightly safer by excluding null/undefined when present, without affecting callers that omit findResource.

-  resource?: TResource;
+  resource?: NonNullable<TResource>;

This maintains backward compatibility because the property is optional, but helps downstream code avoid accidental null checks when a resource is guaranteed (i.e., when findResource is provided).


706-712: Align 404 response shape with loader for consistency (message and optional retry hint).

Minor UX nit: loader returns { error: "Not found" } and (optionally) sets x-should-retry. Action currently returns { error: "Resource not found" } with no header.

Minimal alignment to match message:

-          json({ error: "Resource not found" }, { status: 404 }),
+          json({ error: "Not found" }, { status: 404 }),

If you want parity with loader’s retry hint, add an optional shouldRetryNotFound to action options and include the header:

Type addition (outside this hunk):

// In ApiKeyActionRouteBuilderOptions<...>
shouldRetryNotFound?: boolean;

Destructure it from options (near Lines 525-535):

const { /* ... */, shouldRetryNotFound } = options;

Header usage (in this hunk):

-          json({ error: "Not found" }, { status: 404 }),
+          json(
+            { error: "Not found" },
+            { status: 404, headers: { "x-should-retry": shouldRetryNotFound ? "true" : "false" } }
+          ),

702-705: If you accept body in findResource, pass it through here.

Follow-up to the earlier suggestion to include body in the findResource signature.

-      const resource = options.findResource
-        ? await options.findResource(parsedParams, authenticationResult, parsedSearchParams)
-        : undefined;
+      const resource = options.findResource
+        ? await options.findResource(
+            parsedParams,
+            authenticationResult,
+            parsedSearchParams,
+            parsedBody
+          )
+        : undefined;
packages/cli-v3/install-mcp.sh (7)

3-3: Harden bash safety flags.

Prefer stricter flags to catch unset vars and pipe failures early.

-set -e  # Exit on error
+set -Eeuo pipefail  # Exit on error, fail on unset vars, and on pipe failures

69-76: Prefer command -v over which and verify executability.

command -v is more portable and avoids shelling out to which.

-# Get the absolute path to the node binary
-NODE_PATH=$(which node)
-if [ -z "$NODE_PATH" ]; then
+# Get the absolute path to the node binary
+NODE_PATH="$(command -v node || true)"
+if [ -z "$NODE_PATH" ] || [ ! -x "$NODE_PATH" ]; then
     echo "❌ Error: Node.js not found in PATH"
     echo "Please ensure Node.js is installed and available in your PATH"
     exit 1
 fi

96-98: chmod +x on the ESM entry is unnecessary.

You're invoking Node with the file path; the script bit on the JS file is not needed. Removing avoids mutating repo artifacts.

-# Ensure the CLI is executable
-chmod +x "$CLI_PATH"

121-121: Remove unused require('path').

path is never used in any of the inline Node scripts.

-    const path = require('path');

Also applies to: 196-196, 271-271, 346-346, 421-421, 500-500


174-176: Add cross-platform config paths (macOS/Linux/Windows).

The hardcoded macOS paths will miss Linux and Windows defaults for Claude Desktop and VS Code. Detect OS and set paths accordingly to make installs effective outside macOS.

Example pattern:

# VS Code
case "$OSTYPE" in
  darwin*) VSCODE_DIR="$HOME/Library/Application Support/Code/User" ;;
  linux*)  VSCODE_DIR="$HOME/.config/Code/User" ;;
  msys*|cygwin*|win32*)
           VSCODE_DIR="$(cygpath "$APPDATA")/Code/User" ;;
  *)       VSCODE_DIR="$HOME/.config/Code/User" ;;  # sensible default
esac
VSCODE_CONFIG="$VSCODE_DIR/mcp.json"

# Claude Desktop (only on macOS today; guard accordingly)
if [[ "$OSTYPE" != darwin* ]]; then
  echo "ℹ️ Skipping Claude Desktop install (macOS-only path)"; return 0
fi

Also applies to: 324-326


151-151: Create a backup before overwriting user configs.

If the JSON is malformed or users want to roll back, having a .bak helps.

-        // Write back to file with proper formatting
-        fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
+        // Backup then write back to file with proper formatting
+        if (fs.existsSync(configPath)) {
+          fs.copyFileSync(configPath, `${configPath}.bak`);
+        }
+        fs.writeFileSync(configPath, JSON.stringify(config, null, 2));

Also applies to: 226-226, 301-301, 376-376, 455-455, 530-530


146-148: Make API URL configurable (don’t hardcode localhost:3030).

Expose an optional -a|--api-url or API_URL env to enable remote/self-hosted setups. Default to http://localhost:3030 for DX.

Minimal changes:

  • Add at top: API_URL="${API_URL:-http://localhost:3030}"
  • Extend arg parsing to accept -a|--api-url VALUE with the same guard as --target.
  • In each Node script, inject const apiUrl = '$API_URL'; and change the args to use apiUrl instead of a hardcoded string:
-            args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', 'http://localhost:3030']
+            args: [cliPath, 'mcp', '--log-file', logFile, '--api-url', apiUrl]

I can prepare a full patch if you want this wired end-to-end across the script.

Also applies to: 221-223, 296-298, 371-373, 451-452, 525-527

apps/webapp/app/v3/taskEventStore.server.ts (1)

237-326: Detailed trace query: ensure deterministic ordering and keep SELECTs symmetric

  • Symmetry: The non-partitioned branch still selects id while the partitioned one does not. It’s harmless at runtime but keeps the two paths divergent. Suggest removing id for consistency.
  • Determinism: Add a secondary ORDER BY (e.g., spanId) to stabilize ordering for identical startTime values.

Apply this diff in the selected range:

@@
-        ORDER BY "startTime" ASC
+        ORDER BY "startTime" ASC, "spanId" ASC
         LIMIT ${env.MAXIMUM_TRACE_SUMMARY_VIEW_COUNT}
@@
-        SELECT
-          "spanId",
+        SELECT
+          "spanId",
           "parentId",
           "runId",
           "idempotencyKey",
           message,
           style,
           "startTime",
@@
-        ORDER BY "startTime" ASC
+        ORDER BY "startTime" ASC, "spanId" ASC
         LIMIT ${env.MAXIMUM_TRACE_SUMMARY_VIEW_COUNT}
apps/webapp/app/v3/eventRepository.server.ts (5)

195-224: Broaden output type in SpanDetailedSummary to match persisted reality

TaskEvent.output can be Attributes | string | boolean | number (and may be null/arrays after rehydration). Typing data.output as Attributes is too narrow and can cause consumer type mismatches.

Apply this diff to widen the type, reusing the existing CreatableEvent type:

 export type SpanDetailedSummary = {
   id: string;
   parentId: string | undefined;
   message: string;
   data: {
     runId: string;
     taskSlug?: string;
     taskPath?: string;
     events: SpanEvents;
     startTime: Date;
     duration: number;
     isError: boolean;
     isPartial: boolean;
     isCancelled: boolean;
     level: NonNullable<CreatableEvent["level"]>;
     environmentType: CreatableEventEnvironmentType;
     workerVersion?: string;
     queueName?: string;
     machinePreset?: string;
     properties?: Attributes;
-    output?: Attributes;
+    output?: CreatableEvent["output"] | null | unknown[];
   };
   children: Array<SpanDetailedSummary>;
 };

1711-1731: Duplicate isAncestorCancelled logic; consider centralizing

This reimplements isAncestorCancelled (apps/webapp/app/utils/taskEvent.ts). Duplication risks drift.

Suggestion:

  • Extract a generic helper (accepting Map<string, { isCancelled: boolean; parentId: string | null }>) into utils and reuse it in both places. That avoids Map vs Map variance issues and consolidates behavior.

1732-1776: Duration calculation helper: reuse and DRY up

This variant mirrors existing logic but with a wider event shape. To reduce divergence, factor this together with findFirstCancelledAncestor into a shared util and reuse here and in getTraceSummary.


1778-1807: findFirstCancelledAncestor duplication

Same rationale as above—move to a shared utility to ensure consistent cancellation semantics across summaries.


1898-1901: Prefer importing nanosecondsToMilliseconds from core or a shared util

A local definition is fine, but we already expose this utility in core; importing avoids subtle drift.

Would you like me to wire an import from packages/core/src/v3 (or add a shared util under app/utils) and update call sites?

packages/cli-v3/src/mcp/mintlifyClient.ts (2)

4-13: Extract endpoint and protocol version into constants

Minor polish: hoist "https://trigger.dev/docs/mcp" and "2025-06-18" into module-level constants so future updates don’t require touching call sites.


64-74: Use unique ids for JSON-RPC requests

Static id: 1 makes correlating logs harder and could collide if a future transport multiplexes requests. A timestamp is sufficient here.

   return {
     jsonrpc: "2.0",
-    id: 1,
+    id: Date.now(),
     method: "tools/call",
     params: {
       name: tool,
       arguments: args,
     },
   };
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)

15-16: Avoid type/value name shadowing for ParamsSchema

type ParamsSchema = z.infer<typeof ParamsSchema> shadows the schema value with the type name. It works but is confusing. Rename the type alias and update references.

-type ParamsSchema = z.infer<typeof ParamsSchema>;
+type Params = z.infer<typeof ParamsSchema>;
@@
-  env: ParamsSchema["env"];
+  env: Params["env"];

Also applies to: 81-83

packages/cli-v3/src/utilities/fileSystem.ts (5)

1-6: Consolidate fs imports and remove unused named import

There are two synchronous fs imports (fsSync and fs) and an unused writeFile. Prefer a single node:fs import for sync APIs and fs/promises for async.

-import fsSync from "fs";
-import fsModule, { writeFile } from "fs/promises";
+import fsModule from "fs/promises";
 import fs from "node:fs";

28-30: Use the same fs import for existsSync

Small consistency fix to use the existing fs import.

-export async function pathExists(path: string): Promise<boolean> {
-  return fsSync.existsSync(path);
-}
+export async function pathExists(path: string): Promise<boolean> {
+  return fs.existsSync(path);
+}

101-104: Align sync read with consolidated fs import

If you remove fsSync, switch this to fs.

 export function readJSONFileSync(path: string) {
-  const fileContents = fsSync.readFileSync(path, "utf8");
+  const fileContents = fs.readFileSync(path, "utf8");
 
   return JSON.parse(fileContents);
 }

125-134: Make TOML read “safe” by catching parse errors

safeReadTomlFile should mirror the resilience of safeReadJSONFile and return undefined on unreadable or invalid content.

-export async function safeReadTomlFile(path: string) {
-  const fileExists = await pathExists(path);
-
-  if (!fileExists) return;
-
-  const fileContents = await readFile(path);
-
-  return parseTOML(fileContents.replace(/\r\n/g, "\n"));
-}
+export async function safeReadTomlFile(path: string) {
+  try {
+    const fileExists = await pathExists(path);
+    if (!fileExists) return;
+    const fileContents = await readFile(path);
+    return parseTOML(fileContents.replace(/\r\n/g, "\n"));
+  } catch {
+    return;
+  }
+}

139-147: Mirror JSONC read behavior: catch parse errors and return undefined

Same reasoning as TOML.

-export async function safeReadJSONCFile(path: string) {
-  const fileExists = await pathExists(path);
-
-  if (!fileExists) return;
-
-  const fileContents = await readFile(path);
-
-  return parseJSONC(fileContents.replace(/\r\n/g, "\n"));
-}
+export async function safeReadJSONCFile(path: string) {
+  try {
+    const fileExists = await pathExists(path);
+    if (!fileExists) return;
+    const fileContents = await readFile(path);
+    return parseJSONC(fileContents.replace(/\r\n/g, "\n"));
+  } catch {
+    return;
+  }
+}
packages/cli-v3/src/mcp/utils.ts (2)

4-4: Remove unused import

loadConfig isn’t used in this file.

-import { loadConfig } from "../config.js";

120-134: Minor: remove unused parameter and simplify ProgressTracker internals

#sendNotification(message) ignores its message parameter and uses this.message. You can drop the parameter and call it without passing message.

-  async updateProgress(progress: number, message?: string) {
+  async updateProgress(progress: number, message?: string) {
     this.progress = progress;
@@
-    await this.#sendNotification(progress, this.message);
+    await this.#sendNotification(progress);
   }
@@
-  async incrementProgress(increment: number, message?: string) {
+  async incrementProgress(increment: number, message?: string) {
     this.progress += increment;
@@
-    await this.#sendNotification(this.progress, this.message);
+    await this.#sendNotification(this.progress);
   }
@@
-  async complete(message?: string) {
+  async complete(message?: string) {
     this.progress = this.total;
@@
-    await this.#sendNotification(this.progress, this.message);
+    await this.#sendNotification(this.progress);
   }
@@
-  async #sendNotification(progress: number, message: string) {
+  async #sendNotification(progress: number) {
     if (!this.progressToken) {
       return;
     }
 
     await this.sendNotification({
       method: "notifications/progress",
       params: {
         progress,
         total: this.total,
         message: this.message,
         progressToken: this.progressToken,
       },
     });
   }

Also applies to: 85-96, 108-114, 92-94, 105-106, 113-114

apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1)

81-89: Use the validated tagName from parsedParams, not raw params

You already validated params; using the parsed value improves consistency and avoids accidental divergence.

   const currentWorker = await findCurrentWorkerFromEnvironment(
     {
       id: runtimeEnv.id,
       type: runtimeEnv.type,
     },
     $replica,
-    params.tagName
+    parsedParams.data.tagName
   );
packages/cli-v3/src/mcp/formatters.ts (2)

22-24: Use standard currency precision for cost

“cents” implies 2 decimal places. Showing 4 is unusual and noisy.

-  if (run.costInCents > 0) {
-    lines.push(`Cost: $${(run.costInCents / 100).toFixed(4)}`);
-  }
+  if (run.costInCents > 0) {
+    lines.push(`Cost: $${(run.costInCents / 100).toFixed(2)}`);
+  }

180-269: Optional: draw proper branch lines for last children

Currently every non-root span uses the same “├─” prefix. If you want tree‑like fidelity, pass an isLast flag to choose “└─” for the last child.

packages/cli-v3/src/mcp/tools/runs.ts (2)

32-39: Make the run-trace fetch non-fatal (trace may be unavailable)

If trace retrieval 404s or fails, we shouldn’t fail the whole tool. Prefer a best‑effort trace.

-    const [runResult, traceResult] = await Promise.all([
-      apiClient.retrieveRun(input.runId),
-      apiClient.retrieveRunTrace(input.runId),
-    ]);
-
-    const formattedRun = formatRun(runResult);
-    const formattedTrace = formatRunTrace(traceResult.trace);
+    const runResult = await apiClient.retrieveRun(input.runId);
+
+    let formattedTrace: string;
+    try {
+      const traceResult = await apiClient.retrieveRunTrace(input.runId);
+      formattedTrace = formatRunTrace(traceResult.trace);
+    } catch {
+      formattedTrace = "Trace is not available for this run.";
+    }
+
+    const formattedRun = formatRun(runResult);

129-131: Guard against invalid date inputs for from/to

new Date(string) can yield Invalid Date; avoid sending garbage to the API.

-    const $from = typeof input.from === "string" ? new Date(input.from) : undefined;
-    const $to = typeof input.to === "string" ? new Date(input.to) : undefined;
+    const fromDate = typeof input.from === "string" ? new Date(input.from) : undefined;
+    const $from = fromDate && !isNaN(fromDate.getTime()) ? fromDate : undefined;
+    const toDate = typeof input.to === "string" ? new Date(input.to) : undefined;
+    const $to = toDate && !isNaN(toDate.getTime()) ? toDate : undefined;
packages/cli-v3/src/mcp/tools/deploys.ts (3)

84-96: Use computed value and broaden version regex; reduce redundant work

  • Reuse lineWithoutAnsi for logs instead of calling strip twice.
  • Make the version regex handle patch versions like 1.2.3.
-      const buildingVersion = lineWithoutAnsi.match(/Building version (\d+\.\d+)/);
+      const buildingVersion = lineWithoutAnsi.match(/Building version (\d+(?:\.\d+)+)/);
@@
-      logs.push(stripAnsi(line));
+      logs.push(lineWithoutAnsi);

Note: The progress increments unbounded per log line can easily overshoot 100. Consider scaling updates or clamping to total for nicer UX.


200-214: Package.json resolution likely points at a file; resolve from directory

getPackageJson expects a package root directory. Pass dirname(resolvedPath) to avoid resolution failures on ESM entry files.

-    const { packageJson } = await getPackageJson(resolvedPath, {
+    const { packageJson } = await getPackageJson(dirname(resolvedPath), {
       test: (filePath) => {

Add this import near the top of the file:

import { dirname } from "node:path";

66-71: Progress total can be exceeded by log volume

Incrementing by 1 per line with a fixed total of 100 often overshoots. Consider:

  • Increasing the total dynamically, or
  • Using checkpoints (build, upload, finalize), or
  • Clamping updates to 100.
packages/cli-v3/src/mcp/tools/tasks.ts (1)

47-56: Optional: format payload schema for readability and size

Serializing payloadSchema inline can be hard to read and may be large. Consider pretty-printing or truncating.

-          contents.push(
-            `- ${task.slug} in ${task.filePath} (payload schema: ${JSON.stringify(
-              task.payloadSchema
-            )})`
-          );
+          const schemaText = JSON.stringify(task.payloadSchema, null, 2);
+          contents.push(`- ${task.slug} in ${task.filePath} (payload schema):\n${schemaText}`);
packages/cli-v3/src/commands/init.ts (2)

259-262: Profile flag formatting bug in “Next steps” message

The ternary is inverted and will print “--profile undefined” when no profile is set.

-    `   1. To start developing, run ${chalk.green(
-      `npx trigger.dev@${cliTag} dev${options.profile ? "" : ` --profile ${options.profile}`}`
-    )} in your project directory`
+    `   1. To start developing, run ${chalk.green(
+      `npx trigger.dev@${cliTag} dev${options.profile ? ` --profile ${options.profile}` : ""}`
+    )} in your project directory`

140-157: Consider continuing init after MCP install instead of returning early

Returning here prevents the project from being initialized for users who chose MCP. Either:

  • fall through to the CLI init flow, or
  • prompt whether to continue with CLI init now.

Minimal change to fall through:

-      return;
+      // Continue with CLI initialization after MCP install

If you prefer a prompt, I can draft the interaction and wiring—say the word.

packages/cli-v3/src/mcp/tools/orgs.ts (2)

144-201: Avoid creating the CLI API client twice.

You can initialize the client once and reuse it for both createProject and getProjectEnv.

Apply this diff:

   handler: toolHandler(InitializeProjectInput.shape, async (input, { ctx }) => {
     ctx.logger?.log("calling initialize_project", { input });
 
-    let projectRef: string | undefined = input.projectRef;
+    let projectRef: string | undefined = input.projectRef;
+    const cliApiClient = await ctx.getCliApiClient();
 
     if (!projectRef) {
       const cwd = input.cwd ?? (await ctx.getCwd());
@@
-      const cliApiClient = await ctx.getCliApiClient();
-
       const project = await cliApiClient.createProject(input.orgParam, {
         name: input.projectName,
       });
@@
-    const cliApiClient = await ctx.getCliApiClient();
-
     const projectEnv = await cliApiClient.getProjectEnv({
       projectRef: projectRef,
       env: "dev",
     });

165-173: Make the “existing config found” message more informative.

Return the detected projectRef and config file path so the user knows what was found.

Apply this diff:

-          return {
-            content: [
-              {
-                type: "text",
-                text: `We found an existing trigger.config.ts file in the current working directory. Skipping initialization.`,
-              },
-            ],
-          };
+          return {
+            content: [
+              {
+                type: "text",
+                text: `We found an existing trigger.config.ts (${config.configFile}) with project ref ${config.project}. Skipping initialization.`,
+              },
+            ],
+          };
packages/cli-v3/src/mcp/schemas.ts (4)

26-39: Conditionally require projectName only when projectRef is absent.

Right now projectName is always required, even if the user supplies projectRef. Make projectName optional and enforce “at least one of projectRef or projectName”.

Apply this diff:

-export const InitializeProjectInput = z.object({
+export const InitializeProjectInput = z
+  .object({
     orgParam: z
       .string()
       .describe(
         "The organization to create the project in, can either be the organization slug or the ID. Use the list_orgs tool to get a list of organizations and ask the user to select one."
       ),
     projectRef: ProjectRefSchema,
-  projectName: z
-    .string()
-    .describe(
-      "The name of the project to create. If projectRef is not provided, we will use this name to create a new project in the organization you select."
-    ),
+    projectName: z
+      .string()
+      .describe(
+        "The name of the project to create. If projectRef is not provided, we will use this name to create a new project in the organization you select."
+      )
+      .optional(),
     cwd: z.string().describe("The current working directory of the project").optional(),
-});
+  })
+  .superRefine((val, ctx) => {
+    if (!val.projectRef && !val.projectName) {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        message: "Provide either projectRef or projectName",
+        path: ["projectName"],
+      });
+    }
+  });

101-107: Enforce tags constraints per the description (max 5, each <=128 chars).

You describe limits but don’t validate them.

Apply this diff:

-      tags: z
-        .array(z.string())
+      tags: z
+        .array(z.string())
         .describe(
           "Tags to add to the task run. Must be less than 128 characters and cannot have more than 5"
         )
-        .optional(),
+        .max(5, "You can set up to 5 tags")
+        .superRefine((tags, ctx) => {
+          for (let i = 0; i < tags.length; i++) {
+            if (tags[i].length > 128) {
+              ctx.addIssue({
+                code: z.ZodIssueCode.too_big,
+                type: "string",
+                maximum: 128,
+                inclusive: true,
+                message: `Tag at index ${i} must be 128 characters or fewer`,
+                path: [i],
+              });
+            }
+          }
+        })
+        .optional(),

132-137: Add bounds to ListRunsInput.limit to match the “Up to 100” description.

Ensure the limit is a positive integer and clamp at 100.

Apply this diff:

   limit: z
     .number()
-    .int()
-    .describe("The number of runs to list in a single page. Up to 100")
+    .int()
+    .min(1)
+    .max(100)
+    .describe("The number of runs to list in a single page. Up to 100")
     .optional(),

8-13: Strengthen ProjectRefSchema with a format check.

The description states it starts with proj_. Add a regex to validate when a value is provided.

Apply this diff:

 export const ProjectRefSchema = z
   .string()
+  .regex(/^proj_[A-Za-z0-9]+$/, "Must start with proj_")
   .describe(
     "The trigger.dev project ref, starts with proj_. We will attempt to automatically detect the project ref if running inside a directory that includes a trigger.config.ts file, or if you pass the --project-ref option to the MCP server."
   )
   .optional();

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
packages/cli-v3/src/mcp/formatters.ts (6)

60-63: Avoid potential undefined access on schedule.generator

run.schedule.generator may be absent depending on schedule type. Accessing generator.expression could throw. Use optional chaining and a fallback.

-  if (run.schedule) {
-    lines.push(`Schedule: ${run.schedule.generator.expression} (${run.schedule.id})`);
-  }
+  if (run.schedule) {
+    const expr = run.schedule.generator?.expression ?? "unknown";
+    const id = run.schedule.id ?? "unknown";
+    lines.push(`Schedule: ${expr} (${id})`);
+  }

80-91: Prevent oversized CLI output for payload/output

Large payloads/outputs can overwhelm the CLI. Truncate the JSON rendering while keeping it useful.

-  if (run.payload) {
-    lines.push(`Payload: ${JSON.stringify(run.payload, null, 2)}`);
+  if (run.payload) {
+    lines.push(`Payload: ${stringifyMaybeTruncate(run.payload)}`);
   } else if (run.payloadPresignedUrl) {
     lines.push(`Payload: (large payload available via presigned URL: ${run.payloadPresignedUrl})`);
   }

-  if (run.output) {
-    lines.push(`Output: ${JSON.stringify(run.output, null, 2)}`);
+  if (run.output) {
+    lines.push(`Output: ${stringifyMaybeTruncate(run.output)}`);
   } else if (run.outputPresignedUrl) {
     lines.push(`Output: (large output available via presigned URL: ${run.outputPresignedUrl})`);
   }

Add this helper outside the selected range:

const MAX_JSON_CHARS = 10_000;

function stringifyMaybeTruncate(value: unknown, maxChars = MAX_JSON_CHARS): string {
  try {
    const s = JSON.stringify(value, null, 2);
    return s.length > maxChars ? `${s.slice(0, maxChars)}... (truncated)` : s;
  } catch {
    return String(value);
  }
}

123-134: formatDateTime should accept string timestamps as well

API dates may arrive as ISO strings. Broaden the signature and parse strings to improve robustness and avoid returning “unknown”.

-function formatDateTime(date: Date | undefined): string {
-  if (!date) return "unknown";
-
-  try {
-    return date
-      .toISOString()
-      .replace("T", " ")
-      .replace(/\.\d{3}Z$/, " UTC");
-  } catch {
-    return "unknown";
-  }
+function formatDateTime(date: Date | string | undefined): string {
+  if (!date) return "unknown";
+
+  try {
+    const d = typeof date === "string" ? new Date(date) : date;
+    if (Number.isNaN(d.getTime())) return "unknown";
+    return d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
+  } catch {
+    return "unknown";
+  }
 }

223-231: Only call Object.keys on objects (properties)

If properties is a string/array, Object.keys can be misleading. Guard by type.

-  if (span.data.properties && Object.keys(span.data.properties).length > 0) {
+  if (
+    span.data.properties &&
+    typeof span.data.properties === "object" &&
+    !Array.isArray(span.data.properties) &&
+    Object.keys(span.data.properties).length > 0
+  ) {
     lines.push(
       `${indent}   Properties: ${JSON.stringify(span.data.properties, null, 2).replace(
         /\n/g,
         "\n" + indent + "     "
       )}`
     );
   }

232-240: Only call Object.keys on objects (output)

Same safeguard for output to avoid odd rendering when it’s a primitive.

-  if (span.data.output && Object.keys(span.data.output).length > 0) {
+  if (
+    span.data.output &&
+    typeof span.data.output === "object" &&
+    Object.keys(span.data.output).length > 0
+  ) {
     lines.push(
       `${indent}   Output: ${JSON.stringify(span.data.output, null, 2).replace(
         /\n/g,
         "\n" + indent + "     "
       )}`
     );
   }

305-305: Annotate paginationInfo as string[]

Prevents implicit any and keeps types tight.

-  const paginationInfo = [];
+  const paginationInfo: string[] = [];
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fb9f716 and fc4bd58.

📒 Files selected for processing (2)
  • packages/cli-v3/src/mcp/formatters.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/runs.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli-v3/src/mcp/tools/runs.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/mcp/formatters.ts
🧬 Code Graph Analysis (1)
packages/cli-v3/src/mcp/formatters.ts (3)
packages/core/src/v3/schemas/api.ts (6)
  • RetrieveRunResponse (815-829)
  • RetrieveRunResponse (831-831)
  • RetrieveRunTraceResponseBody (1285-1290)
  • RetrieveRunTraceResponseBody (1292-1292)
  • ListRunResponseItem (833-836)
  • ListRunResponseItem (838-838)
apps/webapp/app/components/primitives/DateTime.tsx (1)
  • formatDateTime (64-80)
packages/core/src/v3/apiClient/pagination.ts (1)
  • CursorPageResponse (16-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/cli-v3/src/mcp/formatters.ts (1)

193-201: Fields match RetrieveRunTraceSpanSchema – no change needed

I’ve confirmed in packages/core/src/v3/schemas/api.ts that RetrieveRunTraceSpanSchema defines:

  • data.startTime: z.coerce.date()
  • data.duration: z.number()

These align exactly with the CLI formatter’s use of span.data.startTime and span.data.duration. No updates required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (2)

637-639: Clone the request before consuming the body to keep it readable in the handler

Reading request.text() consumes the stream. If handlers (or downstream utilities) attempt to read the body again, it will be empty. Worker action parsing uses request.clone().json() to avoid this; mirror that pattern here.

-        const rawBody = await request.text();
+        const rawBody = await request.clone().text();

If you prefer, you could also parse JSON directly from the clone for parity with worker actions:

const body = bodySchema.safeParse(await request.clone().json());

577-583: CORS omission on 413 (Request body too large)

This early return bypasses wrapResponse, so CORS headers won’t be included when corsStrategy !== "none". Browsers will treat this as a CORS failure.

-          return json({ error: "Request body too large" }, { status: 413 });
+          return await wrapResponse(
+            request,
+            json({ error: "Request body too large" }, { status: 413 }),
+            corsStrategy !== "none"
+          );
packages/cli-v3/src/commands/init.ts (2)

259-262: Bug: profile flag interpolation is inverted

When a profile is NOT set, this renders --profile undefined. When a profile IS set, the flag is omitted. Flip the ternary.

Apply this diff:

-    `   1. To start developing, run ${chalk.green(
-      `npx trigger.dev@${cliTag} dev${options.profile ? "" : ` --profile ${options.profile}`}`
-    )} in your project directory`
+    `   1. To start developing, run ${chalk.green(
+      `npx trigger.dev@${cliTag} dev${options.profile ? ` --profile ${options.profile}` : ""}`
+    )} in your project directory`

326-333: Incorrect path normalization: absolute paths get mangled, config may not match created directory

Stripping a leading “/” to force relativity breaks absolute paths (e.g., “/tmp/trigger” becomes “tmp/trigger”). You also return the original location (possibly absolute) while creating the directory from relativeLocation, risking a mismatch between the config dirs and where files are created.

Apply this diff to properly handle absolute/relative paths and return a project-relative path for config:

-      // Ensure that the path is always relative by stripping leading '/' if present
-      const relativeLocation = location.replace(/^\//, "");
-
-      const triggerDir = resolve(process.cwd(), relativeLocation);
+      // Resolve to an absolute dir, but also compute a path relative to the project
+      const projectCwd = resolve(process.cwd());
+      const triggerDir = isAbsolute(location) ? location : resolve(projectCwd, location);
+      const relativeLocation = relative(projectCwd, triggerDir);
-        return { location, isCustomValue: location !== defaultValue };
+        return { location: relativeLocation, isCustomValue: relativeLocation !== defaultValue };
-      return { location, isCustomValue: location !== defaultValue };
+      return { location: relativeLocation, isCustomValue: relativeLocation !== defaultValue };

And add this import at the top of the file to support isAbsolute (outside the hunk):

// at current imports from "node:path"
import { join, relative, resolve, isAbsolute } from "node:path";

Also applies to: 351-353, 373-374

packages/cli-v3/src/commands/dev.ts (1)

100-104: Await the wrapCommandAction promise in the command action

Without awaiting or returning it, Commander won’t track the async lifecycle or errors, causing lost error handling and potentially incorrect exit codes.

Apply this diff:

-  ).action(async (options) => {
-    wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
-      await devCommand(opts);
-    });
-  });
+  ).action(async (options) => {
+    await wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
+      await devCommand(opts);
+    });
+  });
♻️ Duplicate comments (24)
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (1)

36-43: Handle empty/whitespace search params and avoid “unknown” leaking into UI

Trim and use falsy fallback so whitespace-only values fall back; default clientName to empty string to keep copy clean.

Apply this diff:

-  const url = new URL(request.url);
-  const searchObject = Object.fromEntries(url.searchParams.entries());
-
-  const searchParams = SearchParamsSchema.safeParse(searchObject);
-
-  const source = (searchParams.success ? searchParams.data.source : undefined) ?? "cli";
-  const clientName = (searchParams.success ? searchParams.data.clientName : undefined) ?? "unknown";
+  const url = new URL(request.url);
+  const searchObject = Object.fromEntries(url.searchParams.entries());
+
+  const searchParams = SearchParamsSchema.safeParse(searchObject);
+
+  const source =
+    (searchParams.success ? searchParams.data.source?.trim() : undefined) || "cli";
+  const clientName =
+    (searchParams.success ? searchParams.data.clientName?.trim() : undefined) || "";
packages/cli-v3/src/commands/update.ts (1)

4-4: Fix Windows path bug in test filter by using path.join (import is already present)

The current filter checks for POSIX-style paths ("dist/commonjs", "dist/esm"). On Windows these won’t match due to backslashes. Use join to build platform-aware substrings. This also makes the join import (Line 4) meaningful.

Apply this diff:

     const { packageJson } = await getPackageJson(dirname(resolvedPath), {
       test: (filePath) => {
         // We need to skip any type-marker files
-        if (filePath.includes("dist/commonjs")) {
+        if (filePath.includes(join("dist", "commonjs"))) {
           return false;
         }
 
-        if (filePath.includes("dist/esm")) {
+        if (filePath.includes(join("dist", "esm"))) {
           return false;
         }
 
         return true;
       },
     });

Also applies to: 343-355

apps/webapp/app/v3/eventRepository.server.ts (1)

628-742: Fix properties/output shaping, align duration units, correct internal-event filter, and sort children

  • Properties/output: Casting output as Attributes and manually stripping properties misses our standard unflatten/rehydration and null-handling. Use sanitizedAttributes and rehydrateJson like getSpan does.
  • Duration units: This method converts to ms, while getTraceSummary returns raw ns. Please standardize across both for consistency (or document the difference).
  • Event filter: Use "trigger.dev/" (with slash) to match UI filtering logic.
  • Deterministic ordering: Sort children by startTime for predictable UI rendering.

Apply the following diff within this method:

@@
-        const output = event.output ? (event.output as Attributes) : undefined;
-        const properties = event.properties
-          ? removePrivateProperties(event.properties as Attributes)
-          : {};
+        const output =
+          event.output === null || event.output === undefined
+            ? undefined
+            : rehydrateJson(event.output);
+        const properties = sanitizedAttributes(event.properties) ?? {};
@@
-            events: event.events?.filter((e) => !e.name.startsWith("trigger.dev")),
+            events: event.events?.filter((e) => !e.name.startsWith("trigger.dev/")),
@@
       for (const spanSummary of spanDetailedSummaryMap.values()) {
         if (spanSummary.parentId) {
           const parent = spanDetailedSummaryMap.get(spanSummary.parentId);
           if (parent) {
             parent.children.push(spanSummary);
           }
         }
       }
+      // Stable ordering for UI: sort each node's children by start time
+      for (const node of spanDetailedSummaryMap.values()) {
+        node.children.sort(
+          (a, b) => a.data.startTime.getTime() - b.data.startTime.getTime()
+        );
+      }

Follow-up (units):

  • If milliseconds are desired for detailed summaries, consider converting getTraceSummary durations to ms too (outside this diff), or document the divergence clearly in the API types/docs.

Verification script to locate consumers and check assumptions around duration units and event filtering:

#!/bin/bash
set -e

echo "Usages of TraceDetailedSummary / SpanDetailedSummary:"
rg -n -C2 --type=ts '\bTraceDetailedSummary\b|\bSpanDetailedSummary\b'

echo -e "\nEvent filtering conventions for 'trigger.dev/':"
rg -n -C2 --type=ts "startsWith\\(['\"]trigger\\.dev/"

echo -e "\nDuration conversions in summaries:"
rg -n -C3 --type=ts "getTraceSummary\\(|getTraceDetailedSummary\\(|nanosecondsToMilliseconds\\("
packages/cli-v3/src/commands/mcp.ts (3)

37-41: Return/await wrapCommandAction to avoid unhandled async exit

Commander needs the Promise returned. Not awaiting/returning can cause premature process exit and swallowed errors.

-  ).action(async (options) => {
-    wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => {
-      await mcpCommand(opts);
-    });
-  });
+  ).action(async (options) => {
+    return await wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => {
+      await mcpCommand(opts);
+    });
+  });

45-64: Interactive flow exits early; server never starts and errors are swallowed

The TTY branch returns immediately after install, so the MCP server never starts on success, and failures don’t propagate.

     if (installError) {
       outro(`Failed to install MCP server: ${installError.message}`);
-      return;
+      throw installError; // Propagate error
     }
-
-    return;
+    // Do not return; continue to start the server below
   }

68-73: Use “instructions” (not “description”) when constructing McpServer

The SDK expects an instructions field; using description drops server instructions.

   const server = new McpServer({
     name: serverMetadata.name,
     version: serverMetadata.version,
-    description: serverMetadata.instructions,
+    instructions: serverMetadata.instructions,
   });
packages/cli-v3/src/mcp/context.ts (1)

78-79: Propagate preview branch to ApiClient so x-trigger-branch is sent

You accept branch but don’t pass it to ApiClient; downstream requests won’t include the preview branch header.

-    return new ApiClient(cliApiClient.apiURL, jwt.data.token);
+    return new ApiClient(cliApiClient.apiURL, jwt.data.token, options.branch);
packages/cli-v3/src/utilities/configFiles.ts (1)

114-121: Merge settings instead of overwrite to preserve future flags

Nicely avoids clobbering other settings when updating a single flag.

apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (2)

41-41: Version casing mismatch (“V3” vs “v3”) will hide newly created projects

The loader filters version: "V3" while creation writes "v3". Newly created projects won’t appear in the GET response.

Option A — standardize to lowercase "v3" (matches creation here):

-      version: "V3",
+      version: "v3",

Option B — standardize to uppercase "V3" and also change the creation:

-    version: "v3",
+    version: "V3",

Prefer extracting a shared constant (e.g., PROJECT_VERSION_V3) to prevent future drift across routes and services.

#!/bin/bash
# Audit for version literals across the repo to pick a single source of truth
rg -n --type ts -C2 '\bversion\s*:\s*["'\'']V3["'\'']|\bversion\s*:\s*["'\'']v3["'\'']'

Also applies to: 106-106


95-100: Harden JSON parsing: return 400 on malformed body before Zod

request.json() can throw; guard it and then validate.

-  const body = await request.json();
-  const parsedBody = CreateProjectRequestBody.safeParse(body);
+  let body: unknown;
+  try {
+    body = await request.json();
+  } catch {
+    return json({ error: "Invalid JSON in request body" }, { status: 400 });
+  }
+  const parsedBody = CreateProjectRequestBody.safeParse(body);
packages/cli-v3/src/commands/init.ts (1)

156-157: Don’t abort init after a successful MCP install

The early return prevents continuing the normal init flow after installing the MCP server. This matches a previously raised issue and results in users not getting their project initialized when they choose MCP.

Apply this minimal diff to allow the init flow to continue:

-      return;
+      // Continue with CLI initialization after successful MCP install

If you prefer prompting instead of auto-continuing, I can provide a follow-up patch to ask the user whether to proceed with project initialization.

packages/core/src/v3/schemas/api.ts (6)

1172-1179: Name the plain shape to avoid confusion with Zod schema

ApiDeploymentListParams is a plain object used to build a Zod schema, which is misleading. Rename it to ApiDeploymentListParamsShape.

Apply this diff:

-export const ApiDeploymentListParams = {
+const ApiDeploymentListParamsShape = {
   ...ApiDeploymentCommonShape,
   cursor: ApiDeploymentListPaginationCursor,
   limit: ApiDeploymentListPaginationLimit,
 };
 
-export const ApiDeploymentListOptions = z.object(ApiDeploymentListParams);
+export const ApiDeploymentListOptions = z.object(ApiDeploymentListParamsShape);

32-36: Enforce project ref format to match description

The description says externalRef starts with proj_. Enforce via regex/refinement to make the contract explicit.

Apply this diff:

-  externalRef: z
-    .string()
-    .describe(
-      "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
-    ),
+  externalRef: z
+    .string()
+    .regex(/^proj_[A-Za-z0-9]+$/, "Must start with proj_")
+    .describe(
+      "The external reference for the project, also known as the project ref, a unique identifier starting with proj_"
+    ),

85-93: Export missing type alias for GetWorkerTaskResponse

The schema is exported but its inferred TS type isn’t. This causes friction when consumers want the nested task type.

Apply this diff:

 export const GetWorkerTaskResponse = z.object({
   id: z.string(),
   slug: z.string(),
   filePath: z.string(),
   triggerSource: z.string(),
   createdAt: z.coerce.date(),
   payloadSchema: z.any().nullish(),
 });
+
+export type GetWorkerTaskResponse = z.infer<typeof GetWorkerTaskResponse>;

94-106: Constrain worker.engine to RunEngineVersion

Avoid free-form strings by reusing the shared union.

Apply this diff:

-    engine: z.string().nullish(),
+    engine: RunEngineVersion.nullish(),

1190-1209: Unify git metadata shape with GitMeta

For consistency and stronger typing, use the shared GitMeta schema instead of a lax record.

Apply this diff:

-  git: z.record(z.any()).optional(),
+  git: GitMeta.optional(),

1213-1224: Unify branch.git metadata with GitMeta

Same rationale as deployments.

Apply this diff:

-      git: z.record(z.any()).optional(),
+      git: GitMeta.optional(),
packages/cli-v3/src/mcp/tools/docs.ts (1)

1-1: Fix Zod import (no default export)

Zod doesn’t have a default export. Use a named import.

Apply this diff:

-import z from "zod";
+import { z } from "zod";
packages/cli-v3/src/mcp/tools/previewBranches.ts (2)

18-21: Passing configPath to getProjectRef is OK (previous concern now handled).

Given McpContext.getProjectDir now normalizes file paths to directories, passing input.configPath is correct; no change needed.


14-16: Dev-only guard blocks valid dev usage—remove unconditional block.

This tool is not environment-scoped, so an unconditional --dev-only guard prevents legitimate dev usage and is inconsistent with other tools.

Apply this diff to remove the unconditional block:

-    if (ctx.options.devOnly) {
-      return respondWithError(`This MCP server is only available for the dev environment. `);
-    }
packages/cli-v3/src/mcp/tools/deploys.ts (3)

122-126: Dev-only guard for list_deploys should only block non-dev environments.

This unconditionally errors when --dev-only is set, even for dev. Align with other tools.

Apply this diff:

-    if (ctx.options.devOnly) {
+    if (ctx.options.devOnly && input.environment !== "dev") {
       return respondWithError(
         `This MCP server is only available for the dev environment. You tried to access the ${input.environment} environment. Remove the --dev-only flag to access other environments.`
       );
     }

72-80: Child process drops parent env (PATH lost).

Only TRIGGER_MCP_SERVER and CI are set; dropping process.env can break npx/sub-process resolution and proxies.

Apply this diff to merge the parent environment:

-    const deployProcess = x(nodePath, [cliPath, ...args], {
-      nodeOptions: {
-        cwd: cwd.cwd,
-        env: {
-          TRIGGER_MCP_SERVER: "1",
-          CI: "true",
-        },
-      },
-    });
+    const deployProcess = x(nodePath, [cliPath, ...args], {
+      nodeOptions: {
+        cwd: cwd.cwd,
+        env: {
+          ...process.env,
+          TRIGGER_MCP_SERVER: "1",
+          CI: "true",
+        },
+      },
+    });

160-176: Incorrect npx invocation resolution; will not run npx.

Returning [process.argv[0], process.argv[1]] or using process.argv to simulate npx is unreliable and, in the !sdkVersion branch, outright incorrect (you end up running the Node binary as “npx” with the CLI script path). This will fail to spawn the CLI.

Use explicit npx invocations:

   if (!sdkVersion) {
     context.logger?.log("resolve_cli_exec no sdk version found", { cwd });
-
-    return [process.argv[0] ?? "npx", process.argv[1] ?? "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
   }

   if (sdkVersion === VERSION) {
     context.logger?.log("resolve_cli_exec sdk version is the same as the current version", {
       sdkVersion,
     });
-
-    if (typeof process.argv[0] === "string" && typeof process.argv[1] === "string") {
-      return [process.argv[0], process.argv[1]];
-    }
-
-    return ["npx", "trigger.dev@latest"];
+    return ["npx", "trigger.dev@latest"];
   }

Combined with preserving PATH via env merge (above), this ensures x("npx", ["trigger.dev@…", ...args]) works predictably.

packages/cli-v3/src/mcp/utils.ts (1)

2-2: Use named import for Zod (no default export)

Zod doesn’t ship a default export in ESM; default import can break in various bundlers/runtimes. Use named import.

Apply this diff:

-import z from "zod";
+import { z } from "zod";
🧹 Nitpick comments (32)
apps/webapp/app/routes/account.authorization-code.$authorizationCode/route.tsx (3)

18-21: Normalize query params at the schema level (trim to avoid whitespace-only values)

Trim incoming strings so whitespace-only values don’t get treated as “present”.

Apply this diff:

-const SearchParamsSchema = z.object({
-  source: z.string().optional(),
-  clientName: z.string().optional(),
-});
+const SearchParamsSchema = z.object({
+  source: z.string().trim().optional(),
+  clientName: z.string().trim().optional(),
+});

108-114: Add a couple of common client name aliases

Covers more real-world client identifiers without changing behavior for unknowns.

Apply this diff:

 const prettyClientNames: Record<string, string> = {
   "claude-code": "Claude Code",
   "cursor-vscode": "Cursor",
-  "Visual Studio Code": "VSCode",
+  "Visual Studio Code": "VSCode",
+  "VS Code": "VSCode",
+  "vscode": "VSCode",
   "windsurf-client": "Windsurf",
   "claude-ai": "Claude Desktop",
 };

116-124: Optional: narrow source type for clarity

A tiny type refinement documents intent and helps catch typos at call sites without changing runtime.

Apply this diff:

-function getInstructionsForSource(source: string, clientName: string) {
+type AuthSource = "mcp" | "cli";
+function getInstructionsForSource(source: AuthSource | string, clientName: string) {
   if (source === "mcp") {
     if (clientName) {
       return `Return to your ${prettyClientNames[clientName] ?? clientName} to continue.`;
     }
   }
 
   return `Return to your terminal to continue.`;
 }
apps/webapp/app/services/routeBuilders/apiBuilder.server.ts (3)

706-712: Align 404 semantics with loader and expose retry header

The loader returns a consistent "Not found" body and includes the x-should-retry header, while the action path returns "Resource not found" without the header. Aligning these improves client consistency and retry behavior.

Apply this minimal diff for the response body:

-          json({ error: "Resource not found" }, { status: 404 }),
+          json({ error: "Not found" }, { status: 404 }),

Additionally, to fully match loader semantics, consider adding an optional shouldRetryNotFound flag to action options and propagate it into the 404 response header:

Type additions (outside this range):

// 1) Extend ApiKeyActionRouteBuilderOptions with shouldRetryNotFound?:
type ApiKeyActionRouteBuilderOptions<..., TResource = never> = {
  ...
  shouldRetryNotFound?: boolean;
  ...
};

// 2) Pull it from options in createActionApiRoute:
const {
  ...,
  shouldRetryNotFound,
} = options;

Then adjust the 404 in the action to include the header:

return await wrapResponse(
  request,
  json(
    { error: "Not found" },
    { status: 404, headers: { "x-should-retry": shouldRetryNotFound ? "true" : "false" } }
  ),
  corsStrategy !== "none"
);

433-452: Type-safety around optional findResource: consider stronger typing or overloads

Making findResource optional is fine, but it forces handler.resource to be optional even though the 404 guard guarantees it’s present when findResource is provided. If you want better compile-time guarantees, consider either:

  • Changing handler arg to resource: NonNullable | undefined (clearer intent), or
  • Adding overloads to createActionApiRoute so that when options include findResource, the handler receives resource as required (NonNullable).

Minimal local improvement (clarifies intent without API breaks):

-  resource?: TResource;
+  resource: NonNullable<TResource> | undefined;

If you want stricter typing, we can draft overloads so that:

  • Overload A (no findResource): resource is undefined.
  • Overload B (with findResource): resource is NonNullable.

481-483: Handler arg shape: make resource’s intent explicit

Given the new 404 guard, when findResource is provided, the handler will always receive a resource. Encoding that into the type reduces defensive null checks downstream.

Apply this small tweak to clarify intent:

-  resource?: TResource;
+  resource: NonNullable<TResource> | undefined;

Also applies to: 500-501

packages/cli-v3/src/commands/update.ts (2)

336-359: Optional: resolve the package.json directly and drop the custom filter

You can eliminate the dist filter entirely by resolving the package’s package.json directly. This is simpler and more robust across platforms.

Apply this diff:

-    const resolvedPath = nodeResolve.sync(name, {
-      basedir,
-    });
-
-    logger.debug(`Resolved ${name} package version path`, { name, resolvedPath });
-
-    const { packageJson } = await getPackageJson(dirname(resolvedPath), {
-      test: (filePath) => {
-        // We need to skip any type-marker files
-        if (filePath.includes("dist/commonjs")) {
-          return false;
-        }
-
-        if (filePath.includes("dist/esm")) {
-          return false;
-        }
-
-        return true;
-      },
-    });
+    const pkgJsonPath = nodeResolve.sync(`${name}/package.json`, { basedir });
+    logger.debug(`Resolved ${name} package.json path`, { name, pkgJsonPath });
+    const packageJson = await readPackageJSON(pkgJsonPath);

331-339: Default basedir to process.cwd() in tryResolveTriggerPackageVersion

Verified that all existing calls supply a basedir argument, so this change is safe and guards against future omissions:

• packages/cli-v3/src/mcp/tools/deploys.ts:
tryResolveTriggerPackageVersion("@trigger.dev/sdk", cwd)
• packages/cli-v3/src/commands/update.ts:
tryResolveTriggerPackageVersion(name, dirname(packageJsonPath))

Apply this diff:

-export async function tryResolveTriggerPackageVersion(
-  name: string,
-  basedir?: string
-): Promise<string | undefined> {
+export async function tryResolveTriggerPackageVersion(
+  name: string,
+  basedir: string = process.cwd()
+): Promise<string | undefined> {
   try {
     const resolvedPath = nodeResolve.sync(name, { basedir });
apps/webapp/app/v3/eventRepository.server.ts (2)

195-218: Widen output type and align properties type with sanitizer

SpanDetailedSummary.data currently narrows output to Attributes, but rehydrated output can be a primitive (string/number/boolean), null, or a structured object. Also, properties is produced via sanitizedAttributes, which returns unflattened Attributes or undefined. Suggest aligning the types to reflect the actual return shapes.

This is an exported type; please verify downstream usage before changing.

 export type SpanDetailedSummary = {
   id: string;
   parentId: string | undefined;
   message: string;
   data: {
     runId: string;
     taskSlug?: string;
     taskPath?: string;
     events: SpanEvents;
     startTime: Date;
     duration: number;
     isError: boolean;
     isPartial: boolean;
     isCancelled: boolean;
     level: NonNullable<CreatableEvent["level"]>;
     environmentType: CreatableEventEnvironmentType;
     workerVersion?: string;
     queueName?: string;
     machinePreset?: string;
-    properties?: Attributes;
-    output?: Attributes;
+    properties?: ReturnType<typeof sanitizedAttributes>;
+    output?: ReturnType<typeof rehydrateJson>;
   };
   children: Array<SpanDetailedSummary>;
 };

1711-1731: Avoid duplicating isAncestorCancelled; reuse the shared helper

This local copy duplicates logic already available in apps/webapp/app/utils/taskEvent.ts. Prefer importing it to keep behavior consistent and reduce maintenance.

Example (outside this diff):

// at top-level imports
import { isAncestorCancelled } from "~/app/utils/taskEvent";

// remove the local isAncestorCancelled definition

Note: The utils version accepts a Map keyed to PreparedEvent; your PreparedDetailedEvent is structurally compatible. If needed, generalize the utils signature to a minimal structural type (isCancelled, parentId) to support both.

packages/cli-v3/src/commands/mcp.ts (2)

74-85: Hoist fileLogger before oninitialized to avoid temporal coupling

oninitialized captures fileLogger before it’s defined. It currently “works” due to timing, but is fragile. Define fileLogger first.

-  server.server.oninitialized = async () => {
-    fileLogger?.log("initialized mcp command", { options, argv: process.argv });
-  };
-
-  // Start receiving messages on stdin and sending messages on stdout
-  const transport = new StdioServerTransport();
-
-  const fileLogger: FileLogger | undefined = options.logFile
-    ? new FileLogger(options.logFile, server)
-    : undefined;
+  const fileLogger: FileLogger | undefined = options.logFile
+    ? new FileLogger(options.logFile, server)
+    : undefined;
+
+  server.server.oninitialized = async () => {
+    fileLogger?.log("initialized mcp command", { options, argv: process.argv });
+  };
+
+  // Start receiving messages on stdin and sending messages on stdout
+  const transport = new StdioServerTransport();

81-90: Pass devOnly through to the McpContext

You expose a devOnly CLI option but don’t pass it into the context; downstream checks can’t enforce it.

   const context = new McpContext(server, {
     projectRef: options.projectRef,
     fileLogger,
     apiUrl: options.apiUrl ?? CLOUD_API_URL,
     profile: options.profile,
+    devOnly: options.devOnly,
   });
packages/cli-v3/src/mcp/context.ts (1)

106-117: Don’t swallow loadConfig errors; surface actionable message

Ignoring the load error makes troubleshooting impossible and leads to a misleading “No project ref found” error.

-    const [_, config] = await tryCatch(loadConfig({ cwd: projectDir.cwd }));
+    const [loadError, config] = await tryCatch(loadConfig({ cwd: projectDir.cwd }));
+
+    if (loadError) {
+      throw new Error(
+        `Failed to load trigger config from ${projectDir.cwd}: ${
+          loadError instanceof Error ? loadError.message : String(loadError)
+        }`
+      );
+    }
apps/webapp/app/routes/api.v1.orgs.$orgParam.projects.ts (1)

49-51: Optional: return empty list (200) instead of 404 for no projects

prisma.project.findMany returns [] when nothing matches, so this 404 branch is effectively unreachable. Consider removing for clarity, or explicitly handle empty arrays with 200.

packages/cli-v3/src/utilities/fileSystem.ts (1)

54-68: Avoid silently converting arbitrary relative paths to absolute in expandTilde

Expanding "~" is great, but resolving all other paths to absolute may be surprising and alter semantics where relative paths are expected to remain relative.

 export function expandTilde(filePath: string) {
   if (typeof filePath !== "string") {
     throw new TypeError("Path must be a string");
   }

   if (filePath === "~") {
     return homedir();
   }

   if (filePath.startsWith("~/")) {
     return pathModule.resolve(homedir(), filePath.slice(2));
   }

-  return pathModule.resolve(filePath);
+  // Leave other paths unchanged; only expand the tilde.
+  return filePath;
 }
packages/cli-v3/src/commands/init.ts (1)

224-231: Unused --pkg-args flag
The init command still defines a --pkg-args option (and documents it), but the value isn’t passed into installPackages. As a result, any custom package install args provided by users are ignored.

• packages/cli-v3/src/commands/init.ts line 56: defines pkgArgs in the options schema
• packages/cli-v3/src/commands/init.ts line 88: adds --pkg-args <args> to the yargs builder
• docs/cli-init-commands.mdx line 50: documents the --pkg-args flag

Suggested optional refactor:
• Either remove the --pkg-args flag (and its docs) if it’s no longer needed
• Or wire it through to installPackages (e.g., pass options.pkgArgs into your addDependency/addDevDependency calls)

packages/core/src/v3/schemas/api.ts (1)

1165-1171: Pagination: make limit an integer with a default, clarify message

  • Add .int() to enforce integers.
  • Add .default(20) so clients get a predictable value when omitted.
  • Message currently says “less than 100” but .max(100) allows 100; adjust text.

Apply this diff:

-const ApiDeploymentListPaginationLimit = z.coerce
-  .number()
-  .describe("The number of deployments to return, defaults to 20 (max 100)")
-  .min(1, "Limit must be at least 1")
-  .max(100, "Limit must be less than 100")
-  .optional();
+const ApiDeploymentListPaginationLimit = z.coerce
+  .number()
+  .int()
+  .min(1, "Limit must be at least 1")
+  .max(100, "Limit must be at most 100")
+  .default(20);

Note: If you rely on the field being optional elsewhere, keeping .optional() will suppress the default. Prefer removing .optional() so the default applies.

packages/cli-v3/src/mcp/tools/docs.ts (1)

10-14: Avoid duplicating the input shape

z shape is declared twice (once in inputSchema, once in toolHandler). Define it once and reuse for both.

Apply this refactor:

-export const searchDocsTool = {
+const searchDocsShape = { query: z.string() };
+
+export const searchDocsTool = {
   name: toolsMetadata.search_docs.name,
   title: toolsMetadata.search_docs.title,
   description: toolsMetadata.search_docs.description,
-  inputSchema: {
-    query: z.string(),
-  },
-  handler: toolHandler({ query: z.string() }, async (input, { ctx, signal }) => {
+  inputSchema: searchDocsShape,
+  handler: toolHandler(searchDocsShape, async (input, { ctx, signal }) => {
packages/cli-v3/src/mcp/tools/deploys.ts (1)

84-96: Minor: avoid double ANSI stripping per line.

You compute lineWithoutAnsi but still push stripAnsi(line) later. Reuse the computed value to avoid redundant work.

Apply this diff:

-    for await (const line of deployProcess) {
-      const lineWithoutAnsi = stripAnsi(line);
+    for await (const line of deployProcess) {
+      const lineWithoutAnsi = stripAnsi(line);
       const buildingVersion = lineWithoutAnsi.match(/Building version (\d+\.\d+)/);
       if (buildingVersion) {
         await progressTracker.incrementProgress(1, `Building version ${buildingVersion[1]}`);
       } else {
         await progressTracker.incrementProgress(1);
       }
-      logs.push(stripAnsi(line));
+      logs.push(lineWithoutAnsi);
     }
packages/cli-v3/src/commands/install-rules.ts (6)

380-387: Normalize path handling for file writes (absolute vs relative)

Default strategy writes using an absolute path (cwd + relative path) but the claude-code-subagent strategy writes using a relative path. Align these to avoid surprises (e.g., if process.cwd() changes or the call site varies).

Apply this diff:

-  await writeToFile(rulesFilePath, rulesFileContents, "overwrite");
+  const rulesFileAbsolutePath = join(process.cwd(), rulesFilePath);
+  await writeToFile(rulesFileAbsolutePath, rulesFileContents, "overwrite");

147-153: Remove stale/unused types (misleading and out-of-sync with actual results)

InstallRulesResult/InstallRulesResults don’t match actual result shape (you return { clientName, installations }) and they aren’t used. Keeping them increases confusion.

Apply this diff:

-type InstallRulesResults = Array<InstallRulesResult>;
-
-type InstallRulesResult = {
-  configPath: string;
-  clientName: (typeof clients)[number];
-};

286-292: Adjust handleUnsupportedClientOnly signature to void

This function returns an empty array only to be ignored by callers. Return void for clarity and consistency after removing the unused types.

Apply this diff:

-function handleUnsupportedClientOnly(options: InstallRulesCommandOptions): InstallRulesResults {
+function handleUnsupportedClientOnly(options: InstallRulesCommandOptions): void {
   log.info(
     `${cliLink("Install the rules manually", "https://trigger.dev/docs/agents/rules/overview")}`
   );
-
-  return [];
 }

494-518: Ensure type-safe return from multiselect (options list)

multiselect returns unknown | symbol; you later treat the result as RulesManifestVersionOption[]. Add explicit return type and cast after the cancel check to avoid type holes leaking into downstream calls.

Apply this diff:

-async function resolveOptionsForClient(
+async function resolveOptionsForClient(
   clientName: (typeof clients)[number],
   currentVersion: ManifestVersion,
   cmdOptions: InstallRulesCommandOptions
-) {
+): Promise<RulesManifestVersionOption[]> {
   const possibleOptions = currentVersion.options.filter(
     (option) => !option.client || option.client === clientName
   );

-  const selectedOptions = await multiselect({
+  const selectedOptions = (await multiselect({
     message: `Choose the rules you want to install for ${clientLabels[clientName]}`,
     options: possibleOptions.map((option) => ({
       value: option,
       label: option.title,
       hint: `${option.label} [~${option.tokens} tokens]`,
     })),
     required: true,
-  });
+  })) as RulesManifestVersionOption[] | symbol;

   if (isCancel(selectedOptions)) {
     throw new OutroCommandError("No options selected");
   }

-  return selectedOptions;
+  return selectedOptions as RulesManifestVersionOption[];
}

520-557: Ensure type-safe return from multiselect (clients list)

Same multiselect typing concern applies here. Cast after cancel check and return the concrete type.

Apply this diff:

-  const selectedClients = await multiselect({
+  const selectedClients = (await multiselect({
     message: "Select one or more clients to install the rules into",
     options: $selectOptions,
     required: true,
-  });
+  })) as ResolvedClients[] | symbol;

   if (isCancel(selectedClients)) {
     throw new OutroCommandError("No clients selected");
   }

-  return selectedClients;
+  return selectedClients as ResolvedClients[];

139-145: Consider persisting “seen prompt/version” after successful install

You write hasSeenRulesInstallPrompt and last version before attempting installation. If writes succeed but installation fails, the user won’t be prompted again and might miss a later successful attempt.

A safer flow is to write these after installRules(manifest, options) succeeds (wrap in try/catch and only persist on success). If intentional, ignore.

packages/cli-v3/src/commands/dev.ts (1)

110-115: Simplify boolean checks for skip flags

These flags already default to booleans via zod. The typeof checks are redundant.

Apply this diff:

-  const skipMCPInstall = typeof options.skipMCPInstall === "boolean" && options.skipMCPInstall;
+  const skipMCPInstall = options.skipMCPInstall;

-  const skipRulesInstall =
-    typeof options.skipRulesInstall === "boolean" && options.skipRulesInstall;
+  const skipRulesInstall = options.skipRulesInstall;

Also applies to: 143-151

packages/cli-v3/src/mcp/utils.ts (2)

85-93: Clamp progress in updateProgress to [0, total]

incrementProgress clamps, but updateProgress doesn’t. Keep behavior consistent and prevent accidental overshoot.

Apply this diff:

-  async updateProgress(progress: number, message?: string) {
-    this.progress = progress;
+  async updateProgress(progress: number, message?: string) {
+    this.progress = Math.max(0, Math.min(progress, this.total));

120-134: Remove unused parameter or use it consistently in #sendNotification

#sendNotification accepts a message parameter but ignores it, using this.message instead. Either rely solely on instance state or pass the parameter through.

Apply this diff to rely on instance state only:

-  async #sendNotification(progress: number, message: string) {
+  async #sendNotification(progress: number) {
     if (!this.progressToken) {
       return;
     }

     await this.sendNotification({
       method: "notifications/progress",
       params: {
         progress,
         total: this.total,
-        message: this.message,
+        message: this.message,
         progressToken: this.progressToken,
       },
     });
   }

And update callers accordingly:

-    await this.#sendNotification(progress, this.message);
+    await this.#sendNotification(progress);
-    await this.#sendNotification(this.progress, this.message);
+    await this.#sendNotification(this.progress);
-    await this.#sendNotification(this.progress, this.message);
+    await this.#sendNotification(this.progress);
packages/cli-v3/src/rules/manifest.ts (4)

2-2: Prefer node: specifier for built-ins in ESM

For consistency with other files (e.g., install-rules.ts) and clearer intent in ESM, import built-ins via node: specifiers.

Apply this diff:

-import { dirname, join } from "path";
+import { dirname, join } from "node:path";

151-159: Avoid shadowing “path” identifier and improve clarity

Using “path” as a local variable is easy to confuse with module names. Rename to filePath.

Apply this diff:

-  async loadRulesFile(relativePath: string): Promise<string> {
-    const path = join(dirname(this.path), relativePath);
+  async loadRulesFile(relativePath: string): Promise<string> {
+    const filePath = join(dirname(this.path), relativePath);
 
     try {
-      return await readFile(path, "utf8");
+      return await readFile(filePath, "utf8");
     } catch (error) {
       throw new Error(`Failed to load rules file: ${relativePath} - ${error}`);
     }
   }

21-22: Validate installStrategy at schema level (optional)

You’re validating installStrategy later via safeParse and skipping invalid entries. You could instead constrain it in the schema with the RulesFileInstallStrategy enum, simplifying downstream logic.

If desired, change:

installStrategy: z.string().optional(),

to:

installStrategy: RulesFileInstallStrategy.default("default").optional(),

and remove the safeParse branch, relying on schema validation to fail early. This is optional, since your current approach is robust and skips invalid variants gracefully.

Also applies to: 77-85


113-123: Optional: add abort/timeout to fetches

Remote fetches can hang indefinitely in poor network conditions. Consider using AbortController with a timeout to fail fast and provide clearer error messaging.

I can provide a small helper for timed fetch if you’d like to adopt it across loaders.

Also applies to: 125-137

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
packages/cli-v3/src/commands/install-rules.ts (1)

94-96: Fix misleading help text for --branch

This option selects a GitHub branch, not a local file.

-        "--branch <branch>",
-        "The branch to install the rules from. This is useful if you want to install the rules from a local file."
+        "--branch <branch>",
+        "The GitHub branch to install the rules from (rules fetched from triggerdotdev/trigger.dev)."
🧹 Nitpick comments (10)
packages/cli-v3/src/commands/install-rules.ts (7)

2-2: Use a type-only import for ResolvedConfig

Prevents unnecessary runtime import/bundling and aligns with TS best practices.

-import { ResolvedConfig } from "@trigger.dev/core/v3/build";
+import type { ResolvedConfig } from "@trigger.dev/core/v3/build";

76-81: Fix help text: this command installs “rules”, not the “MCP server”

User-facing description currently references MCP server; should say “rules” for clarity.

-      "Choose the client (or clients) to install the MCP server into. We currently support: " +
+      "Choose the client (or clients) to install the rules into. We currently support: " +
         clients.join(", ")

82-85: Typo: “effect” → “affect” in help text

Small polish for professionalism.

-      "The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
+      "The CLI log level to use (debug, info, log, warn, error, none). This does not affect the log level of your trigger.dev tasks.",

116-121: Remove redundant logger level assignment

wrapCommandAction already sets logger.loggerLevel. This duplication is unnecessary.

-      if (opts.logLevel) {
-        logger.loggerLevel = opts.logLevel;
-      }
-
       return await _installRulesCommand(opts);

422-429: Write using absolute path for consistency with the default install pathing

Other install paths use join(process.cwd(), ...). Use the same here to avoid surprises with different working directories.

   const rulesFilePath = ".claude/agents/trigger-dev-task-writer.md";
   const rulesFileContents = option.contents;
 
-  await writeToFile(rulesFilePath, rulesFileContents, "overwrite", option.name);
+  const absolutePath = join(process.cwd(), rulesFilePath);
+  await writeToFile(absolutePath, rulesFileContents, "overwrite", option.name);

431-474: Sanitize option.name before using it in file paths

option.name is embedded in filenames. Even though the official manifest is trusted, defensive slugification avoids path traversal or invalid filename chars when experimenting with local/branch manifests.

Proposed refactor:

 function resolveRulesFilePathForClientOption(
   clientName: (typeof clients)[number],
   option: RulesManifestVersionOption
 ): string {
+  const name = slugifyOptionName(option.name);
   if (option.installStrategy === "claude-code-subagent") {
     return ".claude/agents/trigger-dev-task-writer.md";
   }
 
   switch (clientName) {
     case "claude-code": {
       return "CLAUDE.md";
     }
     case "cursor": {
-      return `.cursor/rules/trigger.${option.name}.mdc`;
+      return `.cursor/rules/trigger.${name}.mdc`;
     }
     case "vscode": {
-      return `.github/instructions/trigger-${option.name}.instructions.md`;
+      return `.github/instructions/trigger-${name}.instructions.md`;
     }
     case "windsurf": {
-      return `.windsurf/rules/trigger-${option.name}.md`;
+      return `.windsurf/rules/trigger-${name}.md`;
     }
     case "gemini-cli": {
       return `GEMINI.md`;
     }
     case "cline": {
-      return `.clinerules/trigger-${option.name}.md`;
+      return `.clinerules/trigger-${name}.md`;
     }
     case "agents.md": {
       return "AGENTS.md";
     }
     case "amp": {
       return "AGENT.md";
     }
     case "kilo": {
-      return `.kilocode/rules/trigger-${option.name}.md`;
+      return `.kilocode/rules/trigger-${name}.md`;
     }
     case "ruler": {
-      return `.ruler/trigger-${option.name}.md`;
+      return `.ruler/trigger-${name}.md`;
     }
     default: {
       throw new Error(`Unknown client: ${clientName}`);
     }
   }
 }

Add helper:

function slugifyOptionName(name: string) {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9._-]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

532-534: Quote frontmatter string values to avoid YAML parsing issues

option.label may contain special characters. Quote strings; leave booleans unquoted.

-function frontmatter(data: Record<string, string | boolean>) {
-  return $output("---", ...Object.entries(data).map(([key, value]) => `${key}: ${value}`), "---");
-}
+function frontmatter(data: Record<string, string | boolean>) {
+  return $output(
+    "---",
+    ...Object.entries(data).map(([key, value]) =>
+      `${key}: ${typeof value === "string" ? JSON.stringify(value) : value}`
+    ),
+    "---"
+  );
+}
packages/cli-v3/src/utilities/fileSystem.ts (3)

54-68: Improve cross-platform tilde expansion and avoid changing non-tilde paths to absolute paths

  • Handle Windows-style "" in addition to "/".
  • Only expand "~" — returning path.resolve(filePath) for non-tilde input may be surprising. Return the original path to preserve caller intent.
 export function expandTilde(filePath: string) {
   if (typeof filePath !== "string") {
     throw new TypeError("Path must be a string");
   }

-  if (filePath === "~") {
+  if (filePath === "~" || filePath === "~\\") {
     return homedir();
   }

-  if (filePath.startsWith("~/")) {
-    return pathModule.resolve(homedir(), filePath.slice(2));
+  if (filePath.startsWith("~/") || filePath.startsWith("~\\")) {
+    return pathModule.join(homedir(), filePath.slice(2));
   }

-  return pathModule.resolve(filePath);
+  // Only expand "~" — don't change non-tilde paths to absolute form.
+  return filePath;
 }

94-98: Use atomic writes for config files and accept buffers

Prevent partial writes by writing to a temp file and renaming in place. Also align signature with createFile by allowing NodeJS.ArrayBufferView.

-// Will create the directory if it doesn't exist
-export async function safeWriteFile(path: string, contents: string) {
-  await fsModule.mkdir(pathModule.dirname(path), { recursive: true });
-  await fsModule.writeFile(path, contents);
-}
+// Will create the directory if it doesn't exist
+export async function safeWriteFile(path: string, contents: string | NodeJS.ArrayBufferView) {
+  await fsModule.mkdir(pathModule.dirname(path), { recursive: true });
+  // Atomic write: write to a temp file in the same directory, then rename.
+  const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
+  try {
+    await fsModule.writeFile(tmp, contents);
+    await fsModule.rename(tmp, path);
+  } finally {
+    // Best-effort cleanup if tmp still exists
+    try {
+      await fsModule.unlink(tmp);
+    } catch {}
+  }
+}

90-93: Apply tilde expansion consistently for read/write helpers

Now that expandTilde exists, use it in writer helpers so ~/... paths work seamlessly. Same applies to the TOML/JSONC writers for consistency.

 export async function writeJSONFile(path: string, json: any, pretty = false) {
-  await safeWriteFile(path, JSON.stringify(json, undefined, pretty ? 2 : undefined));
+  const target = expandTilde(path);
+  await safeWriteFile(target, JSON.stringify(json, undefined, pretty ? 2 : undefined));
 }
 
 export async function writeTomlFile(path: string, toml: any) {
-  await safeWriteFile(path, stringifyTOML(toml));
+  const target = expandTilde(path);
+  await safeWriteFile(target, stringifyTOML(toml));
 }
 
 export async function writeJSONCFile(path: string, json: any) {
-  await safeWriteFile(path, stringifyJSONC(json));
+  const target = expandTilde(path);
+  await safeWriteFile(target, stringifyJSONC(json));
 }

Also applies to: 135-137, 149-151

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b8704b and 8f34277.

📒 Files selected for processing (3)
  • packages/cli-v3/src/commands/install-rules.ts (1 hunks)
  • packages/cli-v3/src/utilities/fileSystem.ts (4 hunks)
  • rules/4.0.0/basic-tasks.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • rules/4.0.0/basic-tasks.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/utilities/fileSystem.ts
  • packages/cli-v3/src/commands/install-rules.ts
🧬 Code Graph Analysis (2)
packages/cli-v3/src/utilities/fileSystem.ts (1)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
packages/cli-v3/src/commands/install-rules.ts (7)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/rules/manifest.ts (7)
  • LocalRulesManifestLoader (140-160)
  • GithubRulesManifestLoader (110-138)
  • loadRulesManifest (99-103)
  • RulesManifest (45-97)
  • currentVersion (59-61)
  • RulesManifestVersionOption (30-38)
  • ManifestVersion (40-43)
packages/cli-v3/src/utilities/configFiles.ts (4)
  • writeConfigLastRulesInstallPromptVersion (144-151)
  • writeConfigHasSeenRulesInstallPrompt (130-137)
  • readConfigHasSeenRulesInstallPrompt (123-128)
  • readConfigLastRulesInstallPromptVersion (139-142)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (3)
  • pathExists (28-30)
  • safeWriteFile (95-98)
  • readFile (50-52)
🪛 ast-grep (0.38.6)
packages/cli-v3/src/commands/install-rules.ts

[warning] 396-399: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->,
"gs"
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/cli-v3/src/commands/install-rules.ts (1)

549-564: Ensure multiselect returns the expected type (Array)

multiselect returns unknown-ish depending on the prompt lib’s typing. Current usage assumes it returns the option objects without validation. If typings are weak, consider an explicit cast or validation to avoid runtime surprises.

If needed, enforce the type:

const selectedOptions = (await multiselect({...})) as Array<RulesManifestVersionOption>;
packages/cli-v3/src/utilities/fileSystem.ts (1)

4-6: Confirmed confbox as a runtime dependency
The packages/cli-v3/package.json file lists "confbox": "^0.2.2" under dependencies, so no changes are needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (10)
packages/cli-v3/src/commands/mcp.ts (3)

52-56: Return/await the action’s Promise to prevent premature process exit.

Commander needs the returned Promise to manage lifecycle and error propagation.

Apply this diff:

   ).action(async (options) => {
-    wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => {
-      await mcpCommand(opts);
-    });
+    return await wrapCommandAction("mcp", McpCommandOptions, options, async (opts) => {
+      await mcpCommand(opts);
+    });
   });

59-86: Interactive flow returns early and never starts the MCP server; also swallow install failures.

  • After the interactive install and rules wizard, the function returns, so the server never starts.
  • On install failure, the error is logged but not propagated.

Continue into the server startup path and propagate install errors. Also avoid forcing loggerLevel to "none" when in interactive mode.

Apply these diffs:

     if (installError) {
       outro(`Failed to install MCP server: ${installError.message}`);
-      return;
+      throw installError;
     }
@@
-    return;
   }
 
-  logger.loggerLevel = "none";
+  if (!process.stdout.isTTY) {
+    logger.loggerLevel = "none";
+  }

Also applies to: 83-84, 86-86


88-92: Use “instructions”, not “description”, when constructing McpServer.

The SDK expects an instructions field; using description will drop the server’s instructions.

Apply this diff:

   const server = new McpServer({
     name: serverMetadata.name,
     version: serverMetadata.version,
-    description: serverMetadata.instructions,
+    instructions: serverMetadata.instructions,
   });
packages/cli-v3/src/commands/install-rules.ts (3)

93-97: Fix misleading help text for --branch.

This selects a GitHub branch, not a local file.

Apply this diff:

       new CommandOption(
         "--branch <branch>",
-        "The branch to install the rules from. This is useful if you want to install the rules from a local file."
+        "The GitHub branch to install the rules from (rules are fetched from triggerdotdev/trigger.dev)."
       ).hideHelp()

147-152: Align InstallRulesResult type with actual usage (uses installations, not configPath).

Prevents type drift and runtime confusion in the results summary logic.

Apply this diff:

 type InstallRulesResults = Array<InstallRulesResult>;
 
 type InstallRulesResult = {
-  configPath: string;
-  targetName: (typeof targets)[number];
+  targetName: (typeof targets)[number];
+  installations: Array<{ option: RulesManifestVersionOption; location: string }>;
 };

397-400: Escape sectionName in RegExp to prevent pattern injection and avoid dotAll reliance.

sectionName originates from manifest data. Escape metacharacters and use a safe body matcher.

Apply this diff:

-        const pattern = new RegExp(
-          `<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->`,
-          "gs"
-        );
+        const escaped = escapeRegExp(sectionName);
+        const pattern = new RegExp(
+          `<!-- TRIGGER.DEV ${escaped} START -->[\\s\\S]*?<!-- TRIGGER.DEV ${escaped} END -->`,
+          "g"
+        );

Add this helper in the module (e.g., below writeToFile):

function escapeRegExp(input: string) {
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
rules/4.0.0/claude-code-agent.md (4)

26-33: Use the v3 SDK import path in examples (tasks import).

The examples should import from "@trigger.dev/sdk/v3" per v3 guidance to avoid mixing SDK versions.

-import { tasks } from "@trigger.dev/sdk";
+import { tasks } from "@trigger.dev/sdk/v3";

47-47: Use the v3 SDK import path for idempotency utilities.

Aligns docs with v3 API.

-import { idempotencyKeys } from "@trigger.dev/sdk";
+import { idempotencyKeys } from "@trigger.dev/sdk/v3";

89-96: Use the v3 SDK import path for runs API.

Ensure all imports consistently target v3.

-import { runs } from "@trigger.dev/sdk";
+import { runs } from "@trigger.dev/sdk/v3";

184-227: Use the v3 SDK import path for defineConfig.

Config helpers live under the v3 entrypoint.

-import { defineConfig } from "@trigger.dev/sdk";
+import { defineConfig } from "@trigger.dev/sdk/v3";
🧹 Nitpick comments (15)
packages/cli-v3/src/commands/install-rules.ts (1)

82-85: Typo: “effect” → “affect”.

Minor grammar tweak in user-facing help text.

Apply this diff:

       "-l, --log-level <level>",
-      "The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
+      "The CLI log level to use (debug, info, log, warn, error, none). This does not affect the log level of your trigger.dev tasks.",
       "log"
rules/4.0.0/realtime.md (3)

46-56: Add missing type-only import for typeof usage in subscribeToRun example

The example uses typeof myTask but never imports that symbol. Add a type-only import to keep the snippet self-contained and compile-clean.

Apply this diff inside the code block:

 import { runs, tasks } from "@trigger.dev/sdk";
+import type { myTask } from "../trigger/tasks";
 
 // Trigger and subscribe
 const handle = await tasks.trigger("my-task", { data: "value" });
 
 // Subscribe to specific run
 for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {

74-86: Import OpenAI chunk type in Streams example for clarity

STREAMS references OpenAI.ChatCompletionChunk without an import. Add the import to make the example copy-paste friendly.

Apply this diff at the top of the code block:

 import { task, metadata } from "@trigger.dev/sdk";
+import type OpenAI from "openai";

120-154: Show TriggerAuthProvider to avoid prop-drilling access tokens in React

Passing accessToken through component props is fine, but we strongly recommend a context provider to standardize token usage and scoping across the app.

Example:

"use client";
import { TriggerAuthProvider } from "@trigger.dev/react-hooks";

export default function App({ accessToken }: { accessToken: string }) {
  return (
    <TriggerAuthProvider accessToken={accessToken}>
      <YourPagesAndComponents />
    </TriggerAuthProvider>
  );
}

Then hooks can omit the accessToken option:

const { submit } = useRealtimeTaskTrigger<typeof myTask>("my-task");

I can add a short subsection with this pattern if you’d like.

rules/4.0.0/basic-tasks.md (2)

88-130: Add guidance: don’t call triggerAndWait outside a task context

The examples correctly use triggerAndWait inside tasks. Consider a one-line warning here that triggerAndWait must be called from within a task run context (not from arbitrary backend code), and advise handling result.ok or using unwrap() with try/catch.


149-151: Use a future-relative date in wait.until example

The fixed date may already be in the past. Suggest using a relative future timestamp to avoid confusion when readers copy-paste.

-    await wait.until({ date: new Date("2024-12-25") });
+    // 30 days from now
+    await wait.until({ date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) });
rules/4.0.0/advanced-tasks.md (4)

7-31: Import runs in tag subscription example

runs.subscribeToRunsWithTag is used but runs isn’t imported.

-import { task, tags } from "@trigger.dev/sdk";
+import { task, tags, runs } from "@trigger.dev/sdk";

105-107: Fix comment: returning value alters retry behavior

The comment says “Allow retry by returning nothing” but the code returns a retryAt override. Adjust comment for accuracy.

-    // Allow retry by returning nothing
-    return { retryAt: new Date(Date.now() + 60000) }; // Retry in 1 minute
+    // Allow retry (use default policy by returning nothing, or override next attempt):
+    return { retryAt: new Date(Date.now() + 60_000) }; // Retry in 1 minute

399-404: Import logger in Usage Monitoring example

logger is used but not imported in this snippet.

-import { task, usage } from "@trigger.dev/sdk";
+import { task, usage, logger } from "@trigger.dev/sdk";

429-438: Add missing import for runs in Run Management section

The snippet uses runs without an import. Add for completeness.

import { runs } from "@trigger.dev/sdk";
rules/4.0.0/scheduled-tasks.md (2)

10-23: Avoid ambiguous variable name “task” and align references

Using export const task can confuse readers given the SDK also exposes task APIs. Rename for clarity and update references.

-export const task = schedules.task({
+export const firstScheduledTask = schedules.task({
   id: "first-scheduled-task",
   run: async (payload) => {
     payload.timestamp; // Date (scheduled time, UTC)
     payload.lastTimestamp; // Date | undefined
     payload.timezone; // IANA, e.g. "America/New_York" (default "UTC")
     payload.scheduleId; // string
     payload.externalId; // string | undefined
     payload.upcoming; // Date[]
 
     payload.timestamp.toLocaleString("en-US", { timeZone: payload.timezone });
   },
 });
 await schedules.create({
-  task: task.id,
+  task: firstScheduledTask.id,
   cron: "0 0 * * *",
   timezone: "America/New_York", // DST-aware
   externalId: "user_123",
   deduplicationKey: "user_123-daily", // updates if reused
 });

Also applies to: 48-55


89-96: Add language to fenced block for cron diagram

Markdown lint (MD040) prefers a language spec. Use text for the ASCII diagram.

-```
+```text
 * * * * *
 | | | | └ day of week (0–7 or 1L–7L; 0/7=Sun; L=last)
 | | | └── month (1–12)
 | | └──── day of month (1–31 or L)
 | └────── hour (0–23)
 └──────── minute (0–59)

</blockquote></details>
<details>
<summary>rules/4.0.0/claude-code-agent.md (3)</summary><blockquote>

`15-15`: **Fix grammar: “it’s” → “its”.**

Possessive pronoun, not a contraction.



```diff
-...as each task gets it's own dedicated process and is charged by the millisecond.
+...as each task gets its own dedicated process and is charged by the millisecond.

23-35: Minor phrasing cleanups for clarity.

Tighten phrasing: “outside a task” and “for example” read better.

-When triggering a task from outside of a task, like for instance from an API handler in a Next.js route, you will use the `tasks.trigger` function and do a type only import of the task instance, to prevent dependencies inside the task file from leaking into the API handler and possibly causing issues with the build. An example:
+When triggering a task from outside a task, for example from an API handler in a Next.js route, use the `tasks.trigger` function and a type-only import of the task instance to prevent dependencies in the task file from leaking into the API handler and possibly causing build issues. For example:

136-156: Avoid redeclaring handle twice in the same snippet.

Readers copying the snippet will hit a redeclaration error. Use a different name for the second example or split into separate code blocks.

-const handle = await processData.trigger(
+const handle2 = await processData.trigger(
   {
     userId: "123",
   },
   {
-    idempotencyKey: "my-idempotency-key", // Scoped across all runs
+    idempotencyKey: "my-idempotency-key", // Scoped across all runs
   }
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f34277 and 1efecdd.

📒 Files selected for processing (9)
  • packages/cli-v3/src/commands/install-rules.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • rules/4.0.0/advanced-tasks.md (1 hunks)
  • rules/4.0.0/basic-tasks.md (1 hunks)
  • rules/4.0.0/claude-code-agent.md (1 hunks)
  • rules/4.0.0/config.md (1 hunks)
  • rules/4.0.0/realtime.md (1 hunks)
  • rules/4.0.0/scheduled-tasks.md (1 hunks)
  • rules/manifest.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • rules/manifest.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/commands/install-rules.ts
  • packages/cli-v3/src/commands/mcp.ts
🧠 Learnings (17)
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)

Applied to files:

  • rules/4.0.0/realtime.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project

Applied to files:

  • rules/4.0.0/realtime.md
  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/config.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks

Applied to files:

  • rules/4.0.0/realtime.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/config.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export every task (including subtasks) defined with task(), schedules.task(), or schemaTask()

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/advanced-tasks.md
  • rules/4.0.0/scheduled-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: When triggering tasks from backend code, prefer tasks.trigger/tasks.batchTrigger/batch.trigger with type-only imports for type safety

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/advanced-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls

Applied to files:

  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/advanced-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Inside tasks, prefer logger.debug/log/info/warn/error over ad-hoc console logging for structured logs

Applied to files:

  • rules/4.0.0/claude-code-agent.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Declare build options and extensions (external, jsx, conditions, extensions) via the build block in trigger.config.ts rather than custom scripts

Applied to files:

  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Provide a valid Trigger.dev configuration using defineConfig with project ref and dirs (e.g., ["./trigger"]; tests/specs auto-excluded)

Applied to files:

  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files

Applied to files:

  • rules/4.0.0/config.md
📚 Learning: 2025-06-06T16:54:23.316Z
Learnt from: nicktrn
PR: triggerdotdev/trigger.dev#2155
File: docs/docs.json:179-183
Timestamp: 2025-06-06T16:54:23.316Z
Learning: In the docs.json configuration for the Trigger.dev documentation (Mintlify system), both "tags": ["v4"] and "tag": "v4" properties can be used together and work correctly, even though this behavior is undocumented and may not work in local development environments.

Applied to files:

  • rules/4.0.0/advanced-tasks.md
🧬 Code Graph Analysis (2)
packages/cli-v3/src/commands/install-rules.ts (7)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/rules/manifest.ts (7)
  • LocalRulesManifestLoader (140-160)
  • GithubRulesManifestLoader (110-138)
  • loadRulesManifest (99-103)
  • RulesManifest (45-97)
  • currentVersion (59-61)
  • RulesManifestVersionOption (30-38)
  • ManifestVersion (40-43)
packages/cli-v3/src/utilities/configFiles.ts (4)
  • writeConfigLastRulesInstallPromptVersion (144-151)
  • writeConfigHasSeenRulesInstallPrompt (130-137)
  • readConfigHasSeenRulesInstallPrompt (123-128)
  • readConfigLastRulesInstallPromptVersion (139-142)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (3)
  • pathExists (28-30)
  • safeWriteFile (95-98)
  • readFile (50-52)
packages/cli-v3/src/commands/mcp.ts (9)
packages/cli-v3/src/cli/common.ts (4)
  • CommonCommandOptions (12-17)
  • CommonCommandOptions (19-19)
  • commonOptions (21-31)
  • wrapCommandAction (45-82)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/commands/install-mcp.ts (1)
  • installMcpServer (189-253)
packages/cli-v3/src/commands/install-rules.ts (1)
  • initiateRulesInstallWizard (160-228)
packages/cli-v3/src/mcp/context.ts (2)
  • logger (33-35)
  • McpContext (24-187)
packages/cli-v3/src/mcp/config.ts (1)
  • serverMetadata (3-10)
packages/cli-v3/src/mcp/logger.ts (1)
  • FileLogger (5-47)
packages/cli-v3/src/consts.ts (1)
  • CLOUD_API_URL (3-3)
packages/cli-v3/src/mcp/tools.ts (1)
  • registerTools (15-49)
🪛 LanguageTool
rules/4.0.0/realtime.md

[grammar] ~258-~258: There might be a mistake here.
Context: ...riptions: - id: Unique run identifier - status: QUEUED, EXECUTING, COMPLETED, `F...

(QB_NEW_EN)


[grammar] ~259-~259: There might be a mistake here.
Context: ... COMPLETED, FAILED, CANCELED, etc. - payload: Task input data (typed) - output: Ta...

(QB_NEW_EN)


[grammar] ~260-~260: There might be a mistake here.
Context: ...tc. - payload: Task input data (typed) - output: Task result (typed, when completed) - ...

(QB_NEW_EN)


[grammar] ~261-~261: There might be a mistake here.
Context: ...ut: Task result (typed, when completed) - metadata: Real-time updatable data - createdAt`...

(QB_NEW_EN)


[grammar] ~262-~262: There might be a mistake here.
Context: ...) - metadata: Real-time updatable data - createdAt, updatedAt: Timestamps - `costInCents...

(QB_NEW_EN)


[grammar] ~263-~263: There might be a mistake here.
Context: ...a - createdAt, updatedAt: Timestamps - costInCents: Execution cost ## Best Practices - *...

(QB_NEW_EN)

rules/4.0.0/basic-tasks.md

[grammar] ~166-~166: There might be a mistake here.
Context: ...pe` for task references when triggering from backend - Waits > 5 seconds: Automa...

(QB_NEW_EN)

rules/4.0.0/claude-code-agent.md

[style] ~8-~8: Consider a different adjective to strengthen your wording.
Context: ...elite Trigger.dev framework expert with deep knowledge of building production-grade ...

(DEEP_PROFOUND)


[grammar] ~15-~15: There might be a mistake here.
Context: ...ocess and is charged by the millisecond. - Always configure the retry property in...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...whether waiting for the results or not). - Use the logger system in Trigger.dev t...

(QB_NEW_EN)


[style] ~23-~23: This phrase is redundant. Consider using “outside”.
Context: ...ring tasks When triggering a task from outside of a task, like for instance from an API h...

(OUTSIDE_OF)


[grammar] ~162-~162: There might be a mistake here.
Context: ... is small-1x which is a 0.5vCPU and 0.5GB of memory. - The default machine preset...

(QB_NEW_EN)


[grammar] ~162-~162: There might be a mistake here.
Context: ... which is a 0.5vCPU and 0.5GB of memory. - The default machine preset can be overri...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ... | vCPU | Memory | Disk space | | :----------------- | :--- | :----- | :...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...--------- | :--- | :----- | :--------- | | micro | 0.25 | 0.25 | 1...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ... | 0.25 | 0.25 | 10GB | | small-1x (default) | 0.5 | 0.5 | 1...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...(default) | 0.5 | 0.5 | 10GB | | small-2x | 1 | 1 | 1...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ... | 1 | 1 | 10GB | | medium-1x | 1 | 2 | 1...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ... | 1 | 2 | 10GB | | medium-2x | 2 | 4 | 1...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ... | 2 | 4 | 10GB | | large-1x | 4 | 8 | 1...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ... | 4 | 8 | 10GB | | large-2x | 8 | 16 | 1...

(QB_NEW_EN)


[grammar] ~229-~229: There might be a mistake here.
Context: ... ``` - Default retry settings for tasks - Default machine preset ## Code Quality ...

(QB_NEW_EN)


[grammar] ~236-~236: There might be a mistake here.
Context: ...ern TypeScript with strict type checking - Follows Trigger.dev's recommended projec...

(QB_NEW_EN)


[grammar] ~237-~237: There might be a mistake here.
Context: ...gger.dev's recommended project structure - Implements comprehensive error handling ...

(QB_NEW_EN)


[grammar] ~238-~238: There might be a mistake here.
Context: ...omprehensive error handling and recovery - Includes inline documentation for comple...

(QB_NEW_EN)


[grammar] ~239-~239: There might be a mistake here.
Context: ...s inline documentation for complex logic - Uses descriptive task IDs following the ...

(QB_NEW_EN)


[grammar] ~240-~240: There might be a mistake here.
Context: ...wing the pattern: 'domain.action.target' - Maintains separation between task logic ...

(QB_NEW_EN)


[grammar] ~241-~241: There might be a mistake here.
Context: ...on between task logic and business logic

(QB_NEW_EN)

rules/4.0.0/config.md

[grammar] ~345-~345: There might be a mistake here.
Context: ... for packages that shouldn't be bundled.

(QB_NEW_EN)

rules/4.0.0/advanced-tasks.md

[grammar] ~158-~158: There might be a mistake here.
Context: ...ts:** - micro: 0.25 vCPU, 0.25 GB RAM - small-1x: 0.5 vCPU, 0.5 GB RAM (default) - `smal...

(QB_NEW_EN)


[grammar] ~159-~159: There might be a mistake here.
Context: ...mall-1x: 0.5 vCPU, 0.5 GB RAM (default) - small-2x: 1 vCPU, 1 GB RAM - medium-1x`: 1 vCPU...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: ...(default) - small-2x: 1 vCPU, 1 GB RAM - medium-1x: 1 vCPU, 2 GB RAM - medium-2x: 2 vCPU...

(QB_NEW_EN)


[grammar] ~161-~161: There might be a mistake here.
Context: ...1 GB RAM - medium-1x: 1 vCPU, 2 GB RAM - medium-2x: 2 vCPU, 4 GB RAM - large-1x: 4 vCPU,...

(QB_NEW_EN)


[grammar] ~162-~162: There might be a mistake here.
Context: ...2 GB RAM - medium-2x: 2 vCPU, 4 GB RAM - large-1x: 4 vCPU, 8 GB RAM - large-2x: 8 vCPU,...

(QB_NEW_EN)


[grammar] ~163-~163: There might be a mistake here.
Context: ... 4 GB RAM - large-1x: 4 vCPU, 8 GB RAM - large-2x: 8 vCPU, 16 GB RAM ## Idempotency ```...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
rules/4.0.0/realtime.md

89-89: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

rules/4.0.0/scheduled-tasks.md

89-89: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 ast-grep (0.38.6)
packages/cli-v3/src/commands/install-rules.ts

[warning] 396-399: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->,
"gs"
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (12)
packages/cli-v3/src/commands/mcp.ts (3)

19-25: Good use of Zod for CLI options (extending common options).

The schema is well-structured and keeps options consistent across commands.


94-96: Nice: initialization logs are scoped and avoid leaking env vars.

Limiting to options and argv (with optional file logger) is a good balance between debug utility and safety.


101-110: Closure over fileLogger is safe here.

oninitialized will run after fileLogger is constructed, so no TDZ issues; just flagging that this ordering is intentional.

packages/cli-v3/src/commands/install-rules.ts (1)

230-284: Wizard flow and result logging look solid.

  • Manifest loader selection (local vs GitHub) is clean.
  • Summarizing installations by location is helpful UX.
rules/4.0.0/realtime.md (2)

21-29: Verify documented default for Public Access Token expirationTime

Comment says default is 15 minutes. Please confirm the current default and any min/max constraints so readers don’t rely on outdated values.

If the default has changed, update the comment accordingly.


259-260: Confirm canonical run status spelling (“CANCELED” vs “CANCELLED”)

Ensure the listed status matches the SDK’s exact string literal. If both appear across docs/UI, standardize on one.

rules/4.0.0/advanced-tasks.md (1)

286-303: Confirm documented payload size thresholds

The limits (512KB general, 10MB max) may evolve. Please verify current thresholds and update the note if needed.

rules/4.0.0/config.md (1)

7-48: Solid, practical config example

Covers required project/dirs, runtime, retries, build settings, and lifecycle hooks succinctly. LGTM.

rules/4.0.0/scheduled-tasks.md (1)

106-113: Confirmed: API method is schedules.del()
The SDK only exports del() (no delete() or remove()), so the snippet in rules/4.0.0/scheduled-tasks.md is correct. No changes required.

rules/4.0.0/claude-code-agent.md (3)

167-176: Confirm machine presets and defaults match the current platform.

Ensure the preset names, resources, and default selection align with what the service actually provisions.

Would you confirm whether small-1x is still the default and that the CPU/memory values in this table reflect current offerings?


44-66: I’m still seeing the full option set in the SDK’s v3 types, including:

  • idempotencyKey, idempotencyKeyTTL
  • delay, ttl
  • queue, machine, maxAttempts, tags, region

They all line up with the docs snippet. No drift detected.

Docs snippet is accurate as-is.


8-9: Do not change the narrative to @trigger.dev/sdk/v3 — this doc targets v4.

The repository search shows rules/4.0.0/claude-code-agent.md and its examples/imports use the v4 package (@trigger.dev/sdk), so replacing the narrative with the v3 import would be inconsistent. Node.js "21+" is correct per the docs/changelogs (e.g., node v21.7.3 / changelog references to node21x).

Files to note:

  • rules/4.0.0/claude-code-agent.md — narrative (lines 8–9) and imports in the same file (e.g. lines ~26, 47, 89, 184) use @trigger.dev/sdk.

Do not apply the suggested diff; keep the original line as-is:
Tasks deployed to Trigger.dev generally run in Node.js 21+ and use the @trigger.dev/sdk package, along with the @trigger.dev/build package...

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.changeset/sharp-dolls-burn.md (2)

5-5: Tighten the release note phrasing (drop “feat:” and clarify parallel structure).

Changesets already handle the intent; the “feat:” prefix is unnecessary, and the sentence reads ambiguously. Recommend clearer, user-facing phrasing.

Apply:

-feat: Add official MCP server, install MCP and rules CLI commands and wizards
+Introduce the official MCP server; add CLI commands and wizards for MCP installation and rules.

6-6: Ensure a trailing newline.

Minor formatting nit to keep markdown files consistent across the repo.

If missing, add a final newline at EOF.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1efecdd and f6c5aa2.

📒 Files selected for processing (1)
  • .changeset/sharp-dolls-burn.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
.changeset/sharp-dolls-burn.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...l MCP and rules CLI commands and wizards

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (4)
packages/cli-v3/src/commands/install-rules.ts (2)

94-96: Fix misleading help text for --branch (matches earlier feedback).

This option selects a GitHub branch, not a local file. The current copy is confusing for users.

Apply this diff:

       new CommandOption(
         "--branch <branch>",
-        "The branch to install the rules from. This is useful if you want to install the rules from a local file."
+        "The GitHub branch to install the rules from (rules fetched from triggerdotdev/trigger.dev)."
       ).hideHelp()

147-153: Type shape mismatch: results expect installations, type defines configPath.

installRules() reads r.installations later, but the InstallRulesResult type only has configPath. This will cause TS type errors and runtime confusion.

Apply this diff:

 type InstallRulesResults = Array<InstallRulesResult>;
 
 type InstallRulesResult = {
-  configPath: string;
-  targetName: (typeof targets)[number];
+  targetName: (typeof targets)[number];
+  installations: Array<{ option: RulesManifestVersionOption; location: string }>;
 };
rules/4.0.0/basic-tasks.md (1)

1-3: v4 import path is correct; avoid reverting to v3

The guidance to use @trigger.dev/sdk (v4) is correct for these rules. Avoid changing examples to @trigger.dev/sdk/v3 (a past suggestion elsewhere is outdated in this context).

rules/4.0.0/claude-code-agent.md (1)

31-38: v4 import path is correct; keep using @trigger.dev/sdk

Given these are v4 rules/docs, sticking with @trigger.dev/sdk is correct. Prior feedback suggesting /v3 doesn’t apply here.

🧹 Nitpick comments (15)
packages/cli-v3/src/commands/install-rules.ts (4)

83-85: Nit: “effect” → “affect” in user-facing help text.

Grammar fix for clarity.

-      "The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
+      "The CLI log level to use (debug, info, log, warn, error, none). This does not affect the log level of your trigger.dev tasks.",

373-377: Standardize absolute vs relative paths when writing files.

Default install uses an absolute path; the Claude Code subagent path uses a relative path. Both work, but consistency improves predictability and logging.

Apply this diff:

 async function performInstallClaudeCodeSubagentOptionForTarget(option: RulesManifestVersionOption) {
   const rulesFilePath = ".claude/agents/trigger-dev-task-writer.md";
   const rulesFileContents = option.contents;

-  await writeToFile(rulesFilePath, rulesFileContents, "overwrite", option.name);
+  const rulesFileAbsolutePath = join(process.cwd(), rulesFilePath);
+  await writeToFile(rulesFileAbsolutePath, rulesFileContents, "overwrite", option.name);

   return { option, location: rulesFilePath };
 }

Also applies to: 422-429


546-558: Gracefully handle zero matching options for a target.

If possibleOptions is empty (e.g., manifest filtered everything for this client), the multiselect will be awkward. Provide a helpful message and skip.

Example change:

   const possibleOptions = currentVersion.options.filter(
     (option) => !option.client || option.client === targetName
   );
 
+  if (possibleOptions.length === 0) {
+    log.message(`${chalk.yellow("⚠")} No rules available for ${targetLabels[targetName]}.`);
+    return [];
+  }
+
   const selectedOptions = await multiselect({

I can wire this in and adjust downstream to seamlessly skip empty selections.


537-539: Simplify $output implementation.

Mapping to identity is redundant.

 function $output(...strings: string[]) {
-  return strings.map((s) => s).join("\n");
+  return strings.join("\n");
 }
packages/cli-v3/src/rules/manifest.ts (2)

2-2: Use node:path import for consistency with ESM Node builtins.

Other files (e.g., install-rules.ts) import from node:path. Aligning imports avoids tooling ambiguity.

-import { dirname, join } from "path";
+import { dirname, join } from "node:path";

65-94: Optional: tighten typing of installStrategy at the schema boundary.

You currently parse a string and safeParse to RulesFileInstallStrategy. You could validate directly with zod to fail fast and simplify downstream.

Illustrative change:

// In the schema
installStrategy: RulesFileInstallStrategy.optional(),

// Then you can drop the safeParse and default directly:
const installStrategyValue = option.installStrategy ?? "default";
return { ...rest, contents, installStrategy: installStrategyValue };

If cross-file imports make this awkward at schema time, your current safeParse approach is fine.

rules/4.0.0/basic-tasks.md (2)

56-63: Verify scheduled task payload fields

The example references payload.timestamp, payload.lastTimestamp, and payload.upcoming. Please confirm these fields exist on the v4 schedules payload and adjust if the data lives elsewhere (e.g., in ctx).

Happy to patch the snippet once you confirm the correct source of these values.


21-21: Prefer structured logging over console.log in tasks

For production observability, prefer the Trigger.dev logger over console.log inside tasks.

If you want, I can rewrite the examples to demonstrate logger usage. Note: I’m intentionally applying our prior “use logger.* inside tasks” learning to these v4 docs (and not the v3 import-path learning).

Also applies to: 57-59, 143-143, 159-159

rules/4.0.0/config.md (3)

295-301: Align machine property naming with other docs

Elsewhere we use machine (not defaultMachine). For consistency across the v4 docs, prefer machine.

 export default defineConfig({
   // ... other config
-  defaultMachine: "large-1x", // Default machine for all tasks
+  machine: "large-1x", // Default machine for all tasks
   maxDuration: 300, // Default max duration (seconds)
   enableConsoleLogging: true, // Console logging in development
 });

94-97: Clarify how to access the Python runtime in tasks

The snippet uses python.runInline / python.runScript without showing where python comes from. Readers may assume an implicit global.

  • If provided by the extension at runtime: add a short note indicating how it’s accessed.
  • If it requires an import or context injection: include the import or show the run signature that exposes it.

I can update the examples once you confirm the intended access pattern.


28-34: Consider documenting build.external for native add-ons

Best Practices mention adding native modules to build.external, but the “Basic Configuration” omits it. Adding it (empty by default) helps users discover it.

   build: {
     autoDetectExternal: true,
     keepNames: true,
     minify: false,
+    // Modules with native add-ons that should not be bundled
+    external: [],
     extensions: [], // Build extensions go here
   },
rules/4.0.0/claude-code-agent.md (4)

16-21: Grammar and clarity: fix “it’s” and tighten phrasing

Minor nits for readability and correctness.

- - Break complex workflows into subtasks that can be independently retried and made idempotent, but don't overly complicate your tasks with too many subtasks. Sometimes the correct approach is to NOT use a subtask and do things like await Promise.allSettled to do work in parallel so save on costs, as each task gets it's own dedicated process and is charged by the millisecond.
+ - Break complex workflows into subtasks that can be independently retried and made idempotent, but don't over-complicate with too many subtasks. Sometimes the correct approach is NOT to use a subtask and instead use `await Promise.allSettled` to run work in parallel to save costs, as each task gets its own dedicated process and is billed by the millisecond.

28-34: Style: “outside a task” is more concise

Tiny style improvement to the intro sentence.

-When triggering a task from outside of a task, like for instance from an API handler in a Next.js route, you will use the `tasks.trigger` function and do a type only import of the task instance, to prevent dependencies inside the task file from leaking into the API handler and possibly causing issues with the build. An example:
+When triggering a task from outside a task (e.g., an API handler in a Next.js route), use `tasks.trigger` and a type-only import of the task instance to prevent task dependencies from leaking into the handler and affecting the build. Example:

189-197: Unify Python extension import path across docs

This file imports pythonExtension from @trigger.dev/python/extension, whereas config.md uses @trigger.dev/build/extensions/python. Please standardize on the correct path for v4.

If @trigger.dev/build/extensions/python is the intended v4 path, update this import:

-import { pythonExtension } from "@trigger.dev/python/extension";
+import { pythonExtension } from "@trigger.dev/build/extensions/python";

1-6: Front matter readability (optional)

The very long, escaped, multi-line description would be more maintainable as a YAML block scalar (|) without \n and embedded pseudo-XML tags.

I can restructure this header into a readable block scalar if desired.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f6c5aa2 and 220c610.

📒 Files selected for processing (6)
  • packages/cli-v3/src/commands/install-rules.ts (1 hunks)
  • packages/cli-v3/src/rules/manifest.ts (1 hunks)
  • rules/4.0.0/basic-tasks.md (1 hunks)
  • rules/4.0.0/claude-code-agent.md (1 hunks)
  • rules/4.0.0/config.md (1 hunks)
  • rules/manifest.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • rules/manifest.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/cli-v3/src/rules/manifest.ts
  • packages/cli-v3/src/commands/install-rules.ts
🧠 Learnings (14)
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Inside tasks, prefer logger.debug/log/info/warn/error over ad-hoc console logging for structured logs

Applied to files:

  • rules/4.0.0/claude-code-agent.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export every task (including subtasks) defined with task(), schedules.task(), or schemaTask()

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls

Applied to files:

  • rules/4.0.0/claude-code-agent.md
  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: When triggering tasks from backend code, prefer tasks.trigger/tasks.batchTrigger/batch.trigger with type-only imports for type safety

Applied to files:

  • rules/4.0.0/basic-tasks.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Declare build options and extensions (external, jsx, conditions, extensions) via the build block in trigger.config.ts rather than custom scripts

Applied to files:

  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Provide a valid Trigger.dev configuration using defineConfig with project ref and dirs (e.g., ["./trigger"]; tests/specs auto-excluded)

Applied to files:

  • rules/4.0.0/config.md
📚 Learning: 2025-08-18T10:07:17.345Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.345Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files

Applied to files:

  • rules/4.0.0/config.md
🧬 Code Graph Analysis (2)
packages/cli-v3/src/rules/manifest.ts (1)
packages/cli-v3/src/rules/types.ts (2)
  • RulesFileInstallStrategy (3-3)
  • RulesFileInstallStrategy (4-4)
packages/cli-v3/src/commands/install-rules.ts (7)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/rules/manifest.ts (7)
  • LocalRulesManifestLoader (142-162)
  • GithubRulesManifestLoader (112-140)
  • loadRulesManifest (101-105)
  • RulesManifest (47-99)
  • currentVersion (61-63)
  • RulesManifestVersionOption (31-40)
  • ManifestVersion (42-45)
packages/cli-v3/src/utilities/configFiles.ts (4)
  • writeConfigLastRulesInstallPromptVersion (144-151)
  • writeConfigHasSeenRulesInstallPrompt (130-137)
  • readConfigHasSeenRulesInstallPrompt (123-128)
  • readConfigLastRulesInstallPromptVersion (139-142)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (3)
  • pathExists (28-30)
  • safeWriteFile (95-98)
  • readFile (50-52)
🪛 LanguageTool
rules/4.0.0/claude-code-agent.md

[style] ~8-~8: Consider a different adjective to strengthen your wording.
Context: ...elite Trigger.dev framework expert with deep knowledge of building production-grade ...

(DEEP_PROFOUND)


[grammar] ~17-~17: There might be a mistake here.
Context: ...ocess and is charged by the millisecond. - Always configure the retry property in...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...whether waiting for the results or not). - Use the logger system in Trigger.dev t...

(QB_NEW_EN)


[style] ~28-~28: This phrase is redundant. Consider using “outside”.
Context: ...ring tasks When triggering a task from outside of a task, like for instance from an API h...

(OUTSIDE_OF)


[grammar] ~167-~167: There might be a mistake here.
Context: ... is small-1x which is a 0.5vCPU and 0.5GB of memory. - The default machine preset...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ... which is a 0.5vCPU and 0.5GB of memory. - The default machine preset can be overri...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ... | vCPU | Memory | Disk space | | :----------------- | :--- | :----- | :...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...--------- | :--- | :----- | :--------- | | micro | 0.25 | 0.25 | 1...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ... | 0.25 | 0.25 | 10GB | | small-1x (default) | 0.5 | 0.5 | 1...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ...(default) | 0.5 | 0.5 | 10GB | | small-2x | 1 | 1 | 1...

(QB_NEW_EN)


[grammar] ~176-~176: There might be a mistake here.
Context: ... | 1 | 1 | 10GB | | medium-1x | 1 | 2 | 1...

(QB_NEW_EN)


[grammar] ~177-~177: There might be a mistake here.
Context: ... | 1 | 2 | 10GB | | medium-2x | 2 | 4 | 1...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ... | 2 | 4 | 10GB | | large-1x | 4 | 8 | 1...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ... | 4 | 8 | 10GB | | large-2x | 8 | 16 | 1...

(QB_NEW_EN)


[grammar] ~226-~226: There might be a mistake here.
Context: ... ``` - Default retry settings for tasks - Default machine preset ## Code Quality ...

(QB_NEW_EN)

rules/4.0.0/basic-tasks.md

[grammar] ~170-~170: There might be a mistake here.
Context: ...pe` for task references when triggering from backend - Waits > 5 seconds: Automa...

(QB_NEW_EN)

rules/4.0.0/config.md

[grammar] ~341-~341: Ensure spelling is correct
Context: ...nal packages**: Add modules with native addons to the build.external array - **Envir...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~346-~346: There might be a mistake here.
Context: ... for packages that shouldn't be bundled.

(QB_NEW_EN)

🪛 ast-grep (0.38.6)
packages/cli-v3/src/commands/install-rules.ts

[warning] 396-399: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->,
"gs"
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
packages/cli-v3/src/commands/install-rules.ts (1)

70-123: Good command wiring and validation.

  • Zod schema for options is solid and wired via wrapCommandAction.
  • Hidden advanced options (--manifest-path, --branch, --force-wizard) are sensible.
  • Logging level integration looks consistent with the rest of the CLI.
packages/cli-v3/src/rules/manifest.ts (1)

112-140: Solid, isomorphic loader strategy with clear failure modes.

  • Uses fetch to retrieve GitHub content (good alignment with isomorphic guideline).
  • Clear error messages including HTTP status.
  • Local loader resolves relative rule paths from the manifest location and wraps fs errors with context.

Also applies to: 145-161

rules/4.0.0/config.md (1)

56-67: Prisma example looks good

The prisma build extension sample is clear and pragmatic (schema, version pinning, migrations, typed SQL).

rules/4.0.0/claude-code-agent.md (1)

165-171: Machine preset guidance is solid

The default and override guidance is clear and consistent with other sections.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (2)

7-7: Disambiguate schema vs type, normalize createdAt to ISO, and validate response with Zod

  • Avoid schema/type name collision by aliasing the Zod schema and importing the TS type separately.
  • createdAt is a Date here; normalize to ISO string to match typical API contract and to enable runtime validation.
  • Validate the response before returning to catch shape drift.

Apply this diff:

-import { GetWorkerByTagResponse } from "@trigger.dev/core/v3/schemas";
+import { GetWorkerByTagResponse as GetWorkerByTagResponseSchema } from "@trigger.dev/core/v3/schemas";
+import type { GetWorkerByTagResponse } from "@trigger.dev/core/v3/schemas";
@@
   const response: GetWorkerByTagResponse = {
     worker: {
       id: currentWorker.friendlyId,
       version: currentWorker.version,
       engine: currentWorker.engine,
       sdkVersion: currentWorker.sdkVersion,
       cliVersion: currentWorker.cliVersion,
       tasks: tasks.map((task) => ({
         id: task.friendlyId,
         slug: task.slug,
         filePath: task.filePath,
         triggerSource: task.triggerSource,
-        createdAt: task.createdAt,
+        createdAt: task.createdAt.toISOString(),
         payloadSchema: task.payloadSchema,
       })),
     },
     urls,
   };
 
-  return json(response);
+  const parsedResponse = GetWorkerByTagResponseSchema.parse(response);
+  return json(parsedResponse);

Also applies to: 121-138, 140-141


111-118: URL uses environment slug correctly (fixes prior “staging” vs “stg” bug)

Good catch using runtimeEnv.slug rather than the route param. This aligns with v3 path builders.

packages/cli-v3/src/commands/install-mcp.ts (3)

1-1: Good adoption of the cross‑platform spinner wrapper

You removed the direct Clack spinner import and now use our wrapper with the correct zero‑arg factory. Usage looks correct.

Also applies to: 18-18, 321-331


392-418: Normalize path components; current falsy check can silently truncate the path

Breaking the loop on a falsy segment can yield incomplete writes without signaling an error. Filter/validate segments before the loop and remove the in-loop falsy break.

 function applyConfigToExistingConfig(
   existingConfig: any,
   pathComponents: string[],
   config: McpServerConfig
 ) {
-  const clonedConfig = structuredClone(existingConfig);
-
-  let currentValueAtPath = clonedConfig;
-
-  for (let i = 0; i < pathComponents.length; i++) {
-    const currentPathSegment = pathComponents[i];
-
-    if (!currentPathSegment) {
-      break;
-    }
-
-    if (i === pathComponents.length - 1) {
-      currentValueAtPath[currentPathSegment] = config;
-      break;
-    } else {
-      currentValueAtPath[currentPathSegment] = currentValueAtPath[currentPathSegment] || {};
-      currentValueAtPath = currentValueAtPath[currentPathSegment];
-    }
-  }
-
-  return clonedConfig;
+  const clonedConfig = structuredClone(existingConfig);
+  const segments = pathComponents.filter(
+    (p) => typeof p === "string" && p.trim().length > 0
+  );
+
+  if (segments.length === 0) {
+    return clonedConfig;
+  }
+
+  let currentValueAtPath: any = clonedConfig;
+  for (let i = 0; i < segments.length; i++) {
+    const currentPathSegment = segments[i]!;
+    if (i === segments.length - 1) {
+      currentValueAtPath[currentPathSegment] = config;
+    } else {
+      currentValueAtPath[currentPathSegment] =
+        currentValueAtPath[currentPathSegment] || {};
+      currentValueAtPath = currentValueAtPath[currentPathSegment];
+    }
+  }
+
+  return clonedConfig;
 }

443-445: AMP path is flattened due to dot in key; results in incorrect config

Returning ["amp.mcpServers", "trigger"] creates a single key named “amp.mcpServers” rather than a nested { amp: { mcpServers: { trigger: ... }}} structure. This will likely break AMP config consumption.

-    case "amp": {
-      return ["amp.mcpServers", "trigger"];
-    }
+    case "amp": {
+      return ["amp", "mcpServers", "trigger"];
+    }
packages/cli-v3/src/commands/install-rules.ts (3)

94-96: Help text for --branch now correctly describes behavior.

Thanks for fixing the earlier misleading “local file” wording.


147-153: Align result types with actual usage and add proper typing to results arrays

Code below reads r.installations but the current InstallRulesResult type has configPath and no installations. Tighten the types and make results arrays strongly typed.

Apply these diffs:

-type InstallRulesResults = Array<InstallRulesResult>;
-
-type InstallRulesResult = {
-  configPath: string;
-  targetName: (typeof targets)[number];
-};
+type Installation = { option: RulesManifestVersionOption; location: string };
+type InstallRulesResult = {
+  targetName: (typeof targets)[number];
+  installations: Installation[];
+};
+type InstallRulesResults = Array<InstallRulesResult>;
-  const results = [];
+  const results: InstallRulesResults = [];
-  const results = [];
+  const results: Installation[] = [];

Also applies to: 244-245, 335-343


397-400: Escape interpolated RegExp and avoid dotAll to prevent pattern injection and ReDoS

sectionName is untrusted manifest input. Escaping prevents pattern manipulation; [\s\S]*? avoids reliance on the s flag and reduces catastrophic backtracking risk.

Apply this diff:

-        const pattern = new RegExp(
-          `<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->`,
-          "gs"
-        );
+        const escaped = escapeRegExp(sectionName);
+        const pattern = new RegExp(
+          `<!-- TRIGGER.DEV ${escaped} START -->[\\s\\S]*?<!-- TRIGGER.DEV ${escaped} END -->`,
+          "g"
+        );

Add this helper (place it outside the function, e.g., near the bottom of the file):

function escapeRegExp(input: string) {
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
🧹 Nitpick comments (10)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (4)

81-88: Use validated tagName from parsed params

You already validated params; pass the validated tagName instead of the raw route param.

   const currentWorker = await findCurrentWorkerFromEnvironment(
     {
       id: runtimeEnv.id,
       type: runtimeEnv.type,
     },
     $replica,
-    params.tagName
+    parsedParams.data.tagName
   );

17-20: Simplify and harden header extraction

You don’t need Zod for a single optional header here. Using Headers.get() avoids case/duplication concerns and extra object materialization.

-const HeadersSchema = z.object({
-  "x-trigger-branch": z.string().optional(),
-});
+// x-trigger-branch is optional; use Headers.get() for case-insensitive lookup.
-const parsedHeaders = HeadersSchema.safeParse(Object.fromEntries(request.headers));
-
-const branch = parsedHeaders.success ? parsedHeaders.data["x-trigger-branch"] : undefined;
+const branch = request.headers.get("x-trigger-branch") ?? undefined;

Also applies to: 36-39


42-62: Use $replica for read-only project lookup (consistency with other reads)

You’re already using $replica for subsequent reads. Use it here too for consistency and potential read scaling.

-  const project = await prisma.project.findFirst({
+  const project = await $replica.project.findFirst({

40-41: Return 400 for preview env without branch header

getEnvironmentFromEnv returns a 404 here, but the issue is a malformed request (missing required header for preview). Return 400 to communicate the problem clearly.

   const { projectRef, env } = parsedParams.data;
 
+  if (env === "preview" && !branch) {
+    return json(
+      { error: "Preview environment requires a branch. Please set the x-trigger-branch header." },
+      { status: 400 }
+    );
+  }
+
   const envResult = await getEnvironmentFromEnv({
     projectId: project.id,
     userId: authenticationResult.userId,
     env,
     branch,
   });
 
   if (!envResult.success) {
-    return json({ error: envResult.error }, { status: 404 });
+    return json({ error: envResult.error }, { status: 404 });
   }

Also applies to: 68-77

packages/cli-v3/src/commands/install-mcp.ts (4)

116-117: Align Zod default for tag with Commander’s default

Commander sets the default tag to cliTag (“v4-beta” or “latest”), while Zod defaults to cliVersion. This mismatch can confuse downstream logic or tests.

Apply this diff to make both defaults consistent:

-  tag: z.string().default(cliVersion),
+  tag: z.string().default(cliTag),

143-144: Update scope option help text to include “local”

The CLI supports a “local” scope (and you surface it elsewhere), but the help text only lists “user” and “project”.

-    .option("--scope <scope>", "Choose the scope of the MCP server, either user or project")
+    .option("--scope <scope>", "Choose the scope of the MCP server: user, project, or local")

207-208: Type the results array for stronger guarantees

Currently inferred as any[]. Tighten to the declared result type.

-  const results = [];
+  const results: InstallMcpServerResults = [];

321-334: Stop spinner on errors to avoid a stuck UI

If an error is thrown between start and stop, the spinner may remain active. Wrap with try/catch to ensure stop is always called.

   const clientSpinner = spinner();
 
   clientSpinner.start(`Installing in ${clientName}`);
 
-  const scope = await resolveScopeForClient(clientName, options);
-
-  clientSpinner.message(`Installing in ${scope.scope} scope at ${scope.location}`);
-
-  const configPath = await performInstallForClient(clientName, scope, options);
-
-  clientSpinner.stop(`Successfully installed in ${clientName} (${configPath})`);
-
-  return { configPath, clientName, scope };
+  try {
+    const scope = await resolveScopeForClient(clientName, options);
+    clientSpinner.message(`Installing in ${scope.scope} scope at ${scope.location}`);
+    const configPath = await performInstallForClient(clientName, scope, options);
+    clientSpinner.stop(`Successfully installed in ${clientName} (${configPath})`);
+    return { configPath, clientName, scope };
+  } catch (err) {
+    clientSpinner.stop();
+    throw err;
+  }
packages/cli-v3/src/commands/install-rules.ts (2)

423-429: Use absolute path for Claude Code subagent write to match other targets

Consistency reduces surprises when users run the command from different CWDs.

Apply this diff:

   const rulesFilePath = ".claude/agents/trigger-dev-task-writer.md";
   const rulesFileContents = option.contents;

-  await writeToFile(rulesFilePath, rulesFileContents, "overwrite", option.name);
+  const rulesFileAbsolutePath = join(process.cwd(), rulesFilePath);
+  await writeToFile(rulesFileAbsolutePath, rulesFileContents, "overwrite", option.name);

   return { option, location: rulesFilePath };

490-494: Parameter is unused; prefix with underscore to satisfy linters

config isn’t used in this function. Prefix to avoid noUnusedParameters warnings while keeping the signature for future needs.

Apply this diff:

-  config: ResolvedConfig
+  _config: ResolvedConfig
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 220c610 and f25e90a.

📒 Files selected for processing (8)
  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (1 hunks)
  • packages/cli-v3/src/commands/install-mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/install-rules.ts (1 hunks)
  • packages/cli-v3/src/commands/mcp.ts (1 hunks)
  • packages/cli-v3/src/commands/update.ts (3 hunks)
  • packages/cli-v3/src/mcp/formatters.ts (1 hunks)
  • packages/cli-v3/src/mcp/tools/docs.ts (1 hunks)
  • packages/cli-v3/src/mcp/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/cli-v3/src/mcp/tools/docs.ts
  • packages/cli-v3/src/commands/mcp.ts
  • packages/cli-v3/src/commands/update.ts
  • packages/cli-v3/src/mcp/formatters.ts
  • packages/cli-v3/src/mcp/utils.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
  • packages/cli-v3/src/commands/install-rules.ts
  • packages/cli-v3/src/commands/install-mcp.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: In the webapp, all environment variables must be accessed through the env export of env.server.ts, instead of directly accessing process.env.
When importing from @trigger.dev/core in the webapp, never import from the root @trigger.dev/core path; always use one of the subpath exports as defined in the package's package.json.

Files:

  • apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts
🧬 Code Graph Analysis (3)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.workers.$tagName.ts (6)
apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts (1)
  • getEnvironmentFromEnv (73-191)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (105-114)
apps/webapp/app/v3/models/workerDeployment.server.ts (1)
  • findCurrentWorkerFromEnvironment (198-224)
apps/webapp/app/db.server.ts (1)
  • $replica (102-105)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3RunsPath (246-255)
packages/core/src/v3/schemas/api.ts (2)
  • GetWorkerByTagResponse (94-106)
  • GetWorkerByTagResponse (108-108)
packages/cli-v3/src/commands/install-rules.ts (8)
packages/cli-v3/src/cli/index.ts (1)
  • program (21-21)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/rules/manifest.ts (7)
  • LocalRulesManifestLoader (142-162)
  • GithubRulesManifestLoader (112-140)
  • loadRulesManifest (101-105)
  • RulesManifest (47-99)
  • currentVersion (61-63)
  • RulesManifestVersionOption (31-40)
  • ManifestVersion (42-45)
packages/cli-v3/src/utilities/configFiles.ts (4)
  • writeConfigLastRulesInstallPromptVersion (144-151)
  • writeConfigHasSeenRulesInstallPrompt (130-137)
  • readConfigHasSeenRulesInstallPrompt (123-128)
  • readConfigLastRulesInstallPromptVersion (139-142)
packages/cli-v3/src/config.ts (1)
  • loadConfig (33-47)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/fileSystem.ts (3)
  • pathExists (28-30)
  • safeWriteFile (95-98)
  • readFile (50-52)
packages/cli-v3/src/commands/install-mcp.ts (8)
packages/core/src/v3/index.ts (1)
  • VERSION (85-85)
packages/cli-v3/src/cli/index.ts (1)
  • program (21-21)
packages/cli-v3/src/utilities/initialBanner.ts (1)
  • printStandloneInitialBanner (65-84)
packages/cli-v3/src/cli/common.ts (2)
  • wrapCommandAction (45-82)
  • OutroCommandError (35-35)
packages/cli-v3/src/utilities/configFiles.ts (1)
  • writeConfigHasSeenMCPInstallPrompt (114-121)
packages/cli-v3/src/utilities/cliOutput.ts (1)
  • cliLink (140-145)
packages/cli-v3/src/utilities/windows.ts (1)
  • spinner (85-85)
packages/cli-v3/src/utilities/fileSystem.ts (5)
  • expandTilde (54-68)
  • safeReadJSONCFile (139-147)
  • writeJSONFile (90-92)
  • safeReadTomlFile (125-133)
  • writeTomlFile (135-137)
🪛 ast-grep (0.38.6)
packages/cli-v3/src/commands/install-rules.ts

[warning] 396-399: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<!-- TRIGGER.DEV ${sectionName} START -->.*?<!-- TRIGGER.DEV ${sectionName} END -->,
"gs"
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +83 to +84
"The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
"log"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in user-facing help text: “effect” → “affect”

Minor but visible polish for the CLI.

Apply this diff:

-      "The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
+      "The CLI log level to use (debug, info, log, warn, error, none). This does not affect the log level of your trigger.dev tasks.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"The CLI log level to use (debug, info, log, warn, error, none). This does not effect the log level of your trigger.dev tasks.",
"log"
"The CLI log level to use (debug, info, log, warn, error, none). This does not affect the log level of your trigger.dev tasks.",
"log"
🤖 Prompt for AI Agents
In packages/cli-v3/src/commands/install-rules.ts around lines 83 to 84, the
user-facing help text contains a typo: change the word "effect" to "affect" in
the CLI log level description so it reads "This does not affect the log level of
your trigger.dev tasks."; update the string accordingly and run a quick
lint/format check to ensure string quoting/line wrapping remains consistent.

@ericallam ericallam merged commit f086626 into main Aug 20, 2025
26 of 27 checks passed
@ericallam ericallam deleted the feat/mcp-server branch August 20, 2025 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants