From 9942ef4cd70b5bbb93549ebbc6407052258cdc00 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sat, 18 Apr 2026 22:29:07 +0100 Subject: [PATCH] fix(cli): skills bundler resolves caller-relative paths + correct dev layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two correctness fixes caught during end-to-end validation: 1. Resolve skill.path relative to the file that called skills.define(), not the project root. The resource catalog already captures filePath on each SkillManifest — use it to compute the source folder. 2. In dev, copy skill bundles to {workingDir}/.trigger/skills/ (where the worker's cwd resolves). In deploy, keep copying to {outputPath}/.trigger/skills/ so the Dockerfile COPY . /app lands them at /app/.trigger/skills/. Also upgrade the skill-discovery failure log from debug to warn so config mistakes surface in the dev console instead of disappearing silently. Finally, update the ai-chat reference's aiChat.run() to pass chatTools through chat.toStreamTextOptions({ tools: chatTools }) instead of spreading them after — the auto-injected loadSkill/readFile/bash tools would otherwise get overwritten by the explicit tools: chatTools key. --- packages/cli-v3/src/build/buildWorker.ts | 2 +- packages/cli-v3/src/build/bundleSkills.ts | 28 +++++++++++++++++++---- packages/cli-v3/src/dev/devSession.ts | 2 +- references/ai-chat/src/trigger/chat.ts | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/cli-v3/src/build/buildWorker.ts b/packages/cli-v3/src/build/buildWorker.ts index 59b4be295..ddfd8dad8 100644 --- a/packages/cli-v3/src/build/buildWorker.ts +++ b/packages/cli-v3/src/build/buildWorker.ts @@ -120,7 +120,7 @@ export async function buildWorker(options: BuildWorkerOptions) { }); buildManifest = skillsResult.buildManifest; } catch (err) { - logger.debug("Skill bundling failed; continuing without skills", err); + logger.warn("Skill bundling failed; continuing without skills", err); } buildManifest = await notifyExtensionOnBuildComplete(buildContext, buildManifest); diff --git a/packages/cli-v3/src/build/bundleSkills.ts b/packages/cli-v3/src/build/bundleSkills.ts index fc78ef82c..65ad9834a 100644 --- a/packages/cli-v3/src/build/bundleSkills.ts +++ b/packages/cli-v3/src/build/bundleSkills.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; -import { dirname, join, resolve as resolvePath } from "node:path"; +import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path"; import type { BuildManifest, SkillManifest } from "@trigger.dev/core/v3/schemas"; import { copyDirectoryRecursive } from "@trigger.dev/build/internal"; import { indexWorkerManifest } from "../indexing/indexWorkerManifest.js"; @@ -60,8 +60,10 @@ export async function bundleSkills( } catch (err) { // Skill discovery via the indexer is best-effort — if the user's // bundle doesn't load cleanly here the downstream full indexer will - // surface the real error. Warn and continue with no skills. - logger.debug(`[bundleSkills] skill discovery failed: ${(err as Error).message}`); + // surface the real error. Warn so the user sees what went wrong. + logger.warn( + `[bundleSkills] skill discovery failed, skipping skill bundling: ${(err as Error).message}` + ); return { buildManifest, skills: [] }; } @@ -69,10 +71,26 @@ export async function bundleSkills( return { buildManifest, skills: [] }; } - const destinationRoot = join(buildManifest.outputPath, ".trigger", "skills"); + // Destination layout differs between dev and deploy: + // - Dev: the worker runs with cwd = workingDir, so skills must live at + // {workingDir}/.trigger/skills/{id}/ for skill.local() to find them. + // - Deploy: the Dockerfile COPY picks up everything under outputPath into + // /app, so we target {outputPath}/.trigger/skills/{id}/ and the + // container's cwd (/app) resolves correctly. + const destinationRoot = + buildManifest.target === "dev" + ? join(workingDir, ".trigger", "skills") + : join(buildManifest.outputPath, ".trigger", "skills"); for (const skill of skills) { - const sourcePath = resolvePath(workingDir, skill.sourcePath); + // Resolve the skill's source folder relative to the file that called + // `skills.define(...)`. Absolute paths are honored as-is. + const callerDir = skill.filePath + ? dirname(resolvePath(workingDir, skill.filePath)) + : workingDir; + const sourcePath = isAbsolute(skill.sourcePath) + ? skill.sourcePath + : resolvePath(callerDir, skill.sourcePath); const skillMdPath = join(sourcePath, "SKILL.md"); let skillMd: string; diff --git a/packages/cli-v3/src/dev/devSession.ts b/packages/cli-v3/src/dev/devSession.ts index aa2764328..2d6645cd5 100644 --- a/packages/cli-v3/src/dev/devSession.ts +++ b/packages/cli-v3/src/dev/devSession.ts @@ -136,7 +136,7 @@ export async function startDevSession({ }); buildManifest = skillsResult.buildManifest; } catch (err) { - logger.debug("Skill bundling failed during dev rebuild", err); + logger.warn("Skill bundling failed during dev rebuild", err); } buildManifest = await notifyExtensionOnBuildComplete(buildContext, buildManifest); diff --git a/references/ai-chat/src/trigger/chat.ts b/references/ai-chat/src/trigger/chat.ts index 61401bd28..3979a6ed7 100644 --- a/references/ai-chat/src/trigger/chat.ts +++ b/references/ai-chat/src/trigger/chat.ts @@ -495,10 +495,10 @@ export const aiChat = chat ...chat.toStreamTextOptions({ registry, telemetry: clientData?.userId ? { userId: clientData.userId } : undefined, + tools: chatTools, }), model: languageModelForChatTurn(modelOverride), messages: messages, - tools: chatTools, stopWhen: stepCountIs(10), abortSignal: stopSignal, providerOptions: {