Skip to content
Merged
61 changes: 32 additions & 29 deletions packages/cli-v3/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,47 +107,50 @@ export function configureDevCommand(program: Command) {
export async function devCommand(options: DevCommandOptions) {
runtimeChecks();

const skipMCPInstall = typeof options.skipMCPInstall === "boolean" && options.skipMCPInstall;
// Only show these install prompts if the user is in a terminal (not in a Coding Agent)
if (process.stdout.isTTY) {
const skipMCPInstall = typeof options.skipMCPInstall === "boolean" && options.skipMCPInstall;

if (!skipMCPInstall) {
const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();
if (!skipMCPInstall) {
const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();

if (!hasSeenMCPInstallPrompt) {
const installChoice = await confirm({
message: "Would you like to install the Trigger.dev MCP server?",
initialValue: true,
});
if (!hasSeenMCPInstallPrompt) {
const installChoice = await confirm({
message: "Would you like to install the Trigger.dev MCP server?",
initialValue: true,
});

writeConfigHasSeenMCPInstallPrompt(true);
writeConfigHasSeenMCPInstallPrompt(true);

const skipInstall = isCancel(installChoice) || !installChoice;
const skipInstall = isCancel(installChoice) || !installChoice;

if (!skipInstall) {
log.step("Welcome to the Trigger.dev MCP server install wizard 🧙");
if (!skipInstall) {
log.step("Welcome to the Trigger.dev MCP server install wizard 🧙");

const [installError] = await tryCatch(
installMcpServer({
yolo: false,
tag: VERSION as string,
logLevel: options.logLevel,
})
);
const [installError] = await tryCatch(
installMcpServer({
yolo: false,
tag: VERSION as string,
logLevel: options.logLevel,
})
);

if (installError) {
log.error(`Failed to install MCP server: ${installError.message}`);
if (installError) {
log.error(`Failed to install MCP server: ${installError.message}`);
}
}
}
}
}

const skipRulesInstall =
typeof options.skipRulesInstall === "boolean" && options.skipRulesInstall;
const skipRulesInstall =
typeof options.skipRulesInstall === "boolean" && options.skipRulesInstall;

if (!skipRulesInstall) {
await initiateRulesInstallWizard({
manifestPath: options.rulesInstallManifestPath,
branch: options.rulesInstallBranch,
});
if (!skipRulesInstall) {
await initiateRulesInstallWizard({
manifestPath: options.rulesInstallManifestPath,
branch: options.rulesInstallBranch,
});
}
}

const authorization = await login({
Expand Down
1 change: 0 additions & 1 deletion packages/cli-v3/src/mcp/tools/deploys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export const deployTool = {
cwd: cwd.cwd,
env: {
TRIGGER_MCP_SERVER: "1",
CI: "true",
},
},
});
Expand Down
2 changes: 1 addition & 1 deletion packages/cli-v3/src/rules/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class GithubRulesManifestLoader implements RulesManifestLoader {

async loadRulesFile(relativePath: string): Promise<string> {
const response = await fetch(
`https://raw.githubusercontent.com/triggerdotdev/trigger.dev/refs/heads/${this.branch}/${relativePath}`
`https://raw.githubusercontent.com/triggerdotdev/trigger.dev/refs/heads/${this.branch}/rules/${relativePath}`
);

if (!response.ok) {
Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@
"execa": "^8.0.1",
"humanize-duration": "^3.27.3",
"jose": "^5.4.0",
"lodash.get": "^4.4.2",
"nanoid": "3.3.8",
"prom-client": "^15.1.0",
"socket.io": "4.7.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/v3/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,7 @@ export const RetrieveRunTraceSpanSchema = z.object({
runId: z.string(),
taskSlug: z.string().optional(),
taskPath: z.string().optional(),
events: z.array(z.any()),
events: z.array(z.any()).optional(),
startTime: z.coerce.date(),
duration: z.number(),
isError: z.boolean(),
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/v3/utils/ioSerialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js";
import { TriggerTracer } from "../tracer.js";
import { zodfetch } from "../zodfetch.js";
import { flattenAttributes } from "./flattenAttributes.js";
import get from "lodash.get";
import { JSONHeroPath } from "@jsonhero/path";

export type IOPacket = {
data?: string | undefined;
Expand Down Expand Up @@ -536,7 +536,7 @@ export async function replaceSuperJsonPayload(original: string, newPayload: stri
.map(([key]) => key);

const overridenUndefinedKeys = originalUndefinedKeys.filter(
(key) => get(newPayloadObject, key) !== undefined
(key) => getKeyFromObject(newPayloadObject, key) !== undefined
);

Comment on lines 538 to 541
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

Bug: Path lookup returns a Node object, so !== undefined becomes true even when the value is undefined

getKeyFromObject() currently returns the result of jsonHeroPath.first(object), which (per library conventions) is a node wrapper, not the raw value. If the path exists but the value at the path is actually undefined, the node is still truthy, so this condition incorrectly treats it as “overridden”.

This would cause us to strip “undefined” metadata from SuperJSON even when the new payload still contains undefined at that path.

Apply this minimal fix (and optional typo fix) to the filter logic:

-    const overridenUndefinedKeys = originalUndefinedKeys.filter(
-      (key) => getKeyFromObject(newPayloadObject, key) !== undefined
-    );
+    const overriddenUndefinedKeys = originalUndefinedKeys.filter(
+      (key) => getKeyFromObject(newPayloadObject, key) !== undefined
+    );

Note: This relies on getKeyFromObject returning the raw value (undefined when the value is undefined). See suggested fix to getKeyFromObject in a separate comment.

Optionally fix the spelling: “overridden” instead of “overriden” for clarity.

📝 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
const overridenUndefinedKeys = originalUndefinedKeys.filter(
(key) => get(newPayloadObject, key) !== undefined
(key) => getKeyFromObject(newPayloadObject, key) !== undefined
);
const overriddenUndefinedKeys = originalUndefinedKeys.filter(
(key) => getKeyFromObject(newPayloadObject, key) !== undefined
);
🤖 Prompt for AI Agents
In packages/core/src/v3/utils/ioSerialization.ts around lines 538 to 541, the
filter misdetects overrides because getKeyFromObject returns a jsonHero Node
(truthy even when its value is undefined); change the check to inspect the
node's actual value (e.g. use getKeyFromObject(newPayloadObject, key)?.value !==
undefined) so only truly defined values count as overrides, and optionally
correct the variable/name from "overriden" to "overridden".

overridenUndefinedKeys.forEach((key) => {
Expand All @@ -551,3 +551,9 @@ export async function replaceSuperJsonPayload(original: string, newPayload: stri

return superjson.deserialize(newSuperJson);
}

function getKeyFromObject(object: unknown, key: string) {
const jsonHeroPath = new JSONHeroPath(key);

return jsonHeroPath.first(object);
}
8 changes: 0 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading