Skip to content

Commit 7c0bd32

Browse files
committed
fix: address review comments — key caches by project/env, fix profile persistence race, escape backslashes in markdown
1 parent d9f988b commit 7c0bd32

File tree

5 files changed

+51
-23
lines changed

5 files changed

+51
-23
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Fix dev CLI leaking build directories on rebuild, causing disk space accumulation. Deprecated workers are now pruned (capped at 2 retained) when no active runs reference them. The watchdog process also cleans up `.trigger/tmp/` when the dev CLI is killed ungracefully (e.g. SIGKILL from pnpm).

packages/cli-v3/src/mcp/context.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,24 @@ export type McpContextOptions = {
5151
export class McpContext {
5252
public readonly server: McpServer;
5353
public readonly options: McpContextOptions;
54+
private _profileLoaded: Promise<void> | undefined;
55+
private _resolveProfileLoaded: (() => void) | undefined;
5456

5557
constructor(server: McpServer, options: McpContextOptions) {
5658
this.server = server;
5759
this.options = options;
60+
this._profileLoaded = new Promise((resolve) => {
61+
this._resolveProfileLoaded = resolve;
62+
});
5863
}
5964

6065
get logger() {
6166
return this.options.fileLogger;
6267
}
6368

6469
public async getAuth() {
70+
// Wait for project profile to be loaded before authenticating
71+
await this._profileLoaded;
6572
const auth = await mcpAuth({
6673
server: this.server,
6774
defaultApiUrl: this.options.apiUrl,
@@ -212,15 +219,20 @@ export class McpContext {
212219
/**
213220
* Load the persisted profile from the project-scoped .trigger/mcp.json.
214221
* Overrides the default global profile with the project-scoped one.
222+
* Must be called once after initialization — tools wait for this before authenticating.
215223
*/
216224
public async loadProjectProfile() {
217-
const cwd = await this.getCwd();
218-
if (!cwd) return;
219-
220-
const config = readMcpProjectConfig(cwd);
221-
if (config?.profile) {
222-
this.options.profile = config.profile;
223-
this.logger?.log("Loaded project profile", { profile: config.profile, cwd });
225+
try {
226+
const cwd = await this.getCwd();
227+
if (!cwd) return;
228+
229+
const config = readMcpProjectConfig(cwd);
230+
if (config?.profile) {
231+
this.options.profile = config.profile;
232+
this.logger?.log("Loaded project profile", { profile: config.profile, cwd });
233+
}
234+
} finally {
235+
this._resolveProfileLoaded?.();
224236
}
225237
}
226238

packages/cli-v3/src/mcp/tools/dashboards.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { formatQueryResults } from "../formatters.js";
44
import { ListDashboardsInput, RunDashboardQueryInput } from "../schemas.js";
55
import { respondWithError, toolHandler } from "../utils.js";
66

7-
// Cache dashboard listings to avoid refetching on every run_dashboard_query call.
8-
// Dashboards are defined in server code and rarely change.
9-
let dashboardCache: { data: ListDashboardsResponseBody; expiresAt: number } | null = null;
7+
// Cache dashboard listings keyed by project/environment.
8+
const dashboardCache = new Map<string, { data: ListDashboardsResponseBody; expiresAt: number }>();
109
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
1110

1211
export const listDashboardsTool = {
@@ -35,8 +34,9 @@ export const listDashboardsTool = {
3534
branch: input.branch,
3635
});
3736

37+
const cacheKey = `${projectRef}:${input.environment}:${input.branch ?? ""}`;
3838
const result = await apiClient.listDashboards();
39-
dashboardCache = { data: result, expiresAt: Date.now() + CACHE_TTL_MS };
39+
dashboardCache.set(cacheKey, { data: result, expiresAt: Date.now() + CACHE_TTL_MS });
4040

4141
const content: string[] = ["## Available Dashboards", ""];
4242

@@ -90,12 +90,14 @@ export const runDashboardQueryTool = {
9090
});
9191

9292
// Use cached dashboard listing if available, otherwise fetch
93+
const cacheKey = `${projectRef}:${input.environment}:${input.branch ?? ""}`;
94+
const cached = dashboardCache.get(cacheKey);
9395
let dashboards: ListDashboardsResponseBody;
94-
if (dashboardCache && Date.now() < dashboardCache.expiresAt) {
95-
dashboards = dashboardCache.data;
96+
if (cached && Date.now() < cached.expiresAt) {
97+
dashboards = cached.data;
9698
} else {
9799
dashboards = await apiClient.listDashboards();
98-
dashboardCache = { data: dashboards, expiresAt: Date.now() + CACHE_TTL_MS };
100+
dashboardCache.set(cacheKey, { data: dashboards, expiresAt: Date.now() + CACHE_TTL_MS });
99101
}
100102
const dashboard = dashboards.dashboards.find((d) => d.key === input.dashboardKey);
101103

packages/cli-v3/src/mcp/tools/profiles.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,19 @@ export const switchProfileTool = {
106106

107107
const previousProfile = ctx.options.profile ?? readAuthConfigCurrentProfileName();
108108
const projectDir = await ctx.getCwd();
109-
ctx.switchProfile(input.profile, projectDir);
109+
110+
// Switch in-memory first (don't persist until auth succeeds)
111+
ctx.switchProfile(input.profile);
110112

111113
// Verify the new profile works by fetching auth
112114
try {
113115
const auth = await ctx.getAuth();
114116

117+
// Auth succeeded — now persist to project config
118+
if (projectDir) {
119+
ctx.switchProfile(input.profile, projectDir);
120+
}
121+
115122
const persisted = projectDir ? " (saved to project)" : " (session only)";
116123

117124
const content = [
@@ -125,8 +132,8 @@ export const switchProfileTool = {
125132
content: [{ type: "text" as const, text: content.join("\n") }],
126133
};
127134
} catch (error) {
128-
// Revert on failure
129-
ctx.switchProfile(previousProfile, projectDir);
135+
// Revert in-memory only (nothing was persisted)
136+
ctx.switchProfile(previousProfile);
130137
return respondWithError(
131138
`Failed to authenticate with profile "${input.profile}". Reverted to "${previousProfile}".`
132139
);

packages/cli-v3/src/mcp/tools/query.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { formatQueryResults } from "../formatters.js";
44
import { QueryInput, QuerySchemaInput } from "../schemas.js";
55
import { respondWithError, toolHandler } from "../utils.js";
66

7-
// Cache query schema (rarely changes)
8-
let schemaCache: { data: QuerySchemaResponseBody; expiresAt: number } | null = null;
7+
// Cache query schema keyed by project/environment (rarely changes)
8+
const schemaCache = new Map<string, { data: QuerySchemaResponseBody; expiresAt: number }>();
99
const SCHEMA_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
1010

1111
export const queryTool = {
@@ -93,7 +93,7 @@ function formatSchemaTable(table: QuerySchemaTable): string {
9393
}
9494

9595
const core = col.coreColumn ? " *" : "";
96-
const desc = parts.join(". ").replace(/\|/g, "\\|");
96+
const desc = parts.join(". ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
9797

9898
lines.push(`| \`${col.name}\`${core} | ${col.type} | ${desc} |`);
9999
}
@@ -130,12 +130,14 @@ export const getQuerySchemaTool = {
130130
branch: input.branch,
131131
});
132132

133+
const cacheKey = `${projectRef}:${input.environment}:${input.branch ?? ""}`;
134+
const cached = schemaCache.get(cacheKey);
133135
let schema: QuerySchemaResponseBody;
134-
if (schemaCache && Date.now() < schemaCache.expiresAt) {
135-
schema = schemaCache.data;
136+
if (cached && Date.now() < cached.expiresAt) {
137+
schema = cached.data;
136138
} else {
137139
schema = await apiClient.getQuerySchema();
138-
schemaCache = { data: schema, expiresAt: Date.now() + SCHEMA_CACHE_TTL_MS };
140+
schemaCache.set(cacheKey, { data: schema, expiresAt: Date.now() + SCHEMA_CACHE_TTL_MS });
139141
}
140142

141143
const table = schema.tables.find((t) => t.name === input.table);

0 commit comments

Comments
 (0)