Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-cli-ui-bugs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@trigger.dev/build": patch
"trigger.dev": patch
"@trigger.dev/core": patch
"@internal/clickhouse": patch
"webapp": patch
Comment on lines +2 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Release tooling will error because the change note lists a package that is excluded from versioning

The new release note lists webapp (.changeset/fix-cli-ui-bugs.md:6) even though that package is explicitly excluded from versioning, so the versioning step fails when it sees excluded and non-excluded packages in the same note.
Impact: The release/versioning job errors out until the note is corrected.

Changesets ignore list

.changeset/config.json contains "ignore": ["webapp", "coordinator", "docker-provider", "kubernetes-provider", "supervisor"]. Changesets fails with "The following changesets contain both ignored and not ignored packages" in that situation. Per CONTRIBUTING.md/CLAUDE.md, server-only changes belong in a .server-changes/ file, not in a changeset. @internal/clickhouse is also a private, unpublished package.

Suggested change
"@trigger.dev/build": patch
"trigger.dev": patch
"@trigger.dev/core": patch
"@internal/clickhouse": patch
"webapp": patch
---
"@trigger.dev/build": patch
"trigger.dev": patch
"@trigger.dev/core": patch
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

---

fix: resolve collection of CLI and UI bugs (#3168, #3105, #3139)
13 changes: 4 additions & 9 deletions apps/webapp/app/components/RuntimeIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,10 @@ export function RuntimeIcon({
}: RuntimeIconProps) {
const parsedRuntime = parseRuntime(runtime);

// Default to Node.js if no runtime is specified
const effectiveRuntime = parsedRuntime || {
runtime: "node" as const,
originalRuntime: "node",
displayName: "Node.js",
};

const icon = getIcon(effectiveRuntime.runtime, className);
const formattedText = formatRuntimeWithVersion(effectiveRuntime.originalRuntime, runtimeVersion);
const icon = parsedRuntime ? getIcon(parsedRuntime.runtime, className) : <span className="text-text-dimmed">–</span>;
const formattedText = parsedRuntime
? formatRuntimeWithVersion(parsedRuntime.originalRuntime, runtimeVersion)
: "Unknown";

if (withLabel) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ export async function action({ request }: ActionFunctionArgs) {
}

function createRunReplicationService(params: CreateRunReplicationServiceParams) {
const url = new URL(env.RUN_REPLICATION_CLICKHOUSE_URL);
// Remove secure param to prevent Unknown URL parameters error
url.searchParams.delete("secure");

const clickhouse = new ClickHouse({
url: env.RUN_REPLICATION_CLICKHOUSE_URL,
url: url.toString(),
name: params.name,
keepAlive: {
enabled: params.keepAliveEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ export function ConnectGitHubRepoModal({
<TextLink
target="_blank"
rel="noreferrer noopener"
to={`https://github.com/settings/installations/${selectedInstallation?.appInstallationId}`}
to={`https://github.com/apps/trigger-dev-app/installations/${selectedInstallation?.appInstallationId}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Repository access link points at trigger.dev's hosted GitHub app for every installation

The "configure repository access" link is now built with a hard-coded application name (https://github.com/apps/trigger-dev-app/installations/... at apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx:517) instead of the app that is actually configured, so self-hosted installs send users to the wrong page.
Impact: Self-hosted users clicking the link land on an unrelated GitHub app page and cannot manage their repository access.

Configured app slug is available

The GitHub app slug is configurable via GITHUB_APP_SLUG (apps/webapp/app/env.server.ts:20) and is used elsewhere, e.g. apps/webapp/app/services/gitHubSession.server.ts:37. The component should receive the configured slug (via the resource loader) rather than embedding trigger-dev-app.

Prompt for agents
The hint link hardcodes the GitHub app slug `trigger-dev-app`. The slug is configurable through GITHUB_APP_SLUG (apps/webapp/app/env.server.ts) and should be surfaced from the github resource route loader and used to build the installation URL, so self-hosted deployments with their own GitHub App get a working link.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

>
GitHub
</TextLink>
Expand Down Expand Up @@ -632,9 +632,9 @@ export function ConnectedGitHubRepoForm({
useEffect(() => {
const hasChanges =
gitSettingsValues.productionBranch !==
(connectedGitHubRepo.branchTracking?.prod?.branch || "") ||
(connectedGitHubRepo.branchTracking?.prod?.branch || "") ||
gitSettingsValues.stagingBranch !==
(connectedGitHubRepo.branchTracking?.staging?.branch || "") ||
(connectedGitHubRepo.branchTracking?.staging?.branch || "") ||
gitSettingsValues.previewDeploymentsEnabled !== connectedGitHubRepo.previewDeploymentsEnabled;
setHasGitSettingsChanges(hasChanges);
}, [gitSettingsValues, connectedGitHubRepo]);
Expand Down Expand Up @@ -898,6 +898,6 @@ export function GitHubSettingsPanel({
</Hint>
)}
</div>

);
}
3 changes: 3 additions & 0 deletions apps/webapp/app/services/clickhouseInstance.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ function initializeQueryClickhouseClient() {

const url = new URL(env.QUERY_CLICKHOUSE_URL);

// Remove secure param
url.searchParams.delete("secure");

return new ClickHouse({
url: url.toString(),
name: "query-clickhouse",
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/services/emailAuth.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const emailStrategy = new EmailLinkStrategy(
secret,
callbackURL: "/magic",
sessionMagicLinkKey: "triggerdotdev:magiclink",
validateSession: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Magic-link option name may not match the strategy's API

remix-auth-email-link@2.0.2 exposes the option validateSessionMagicLink (default false); there is no validateSession option. If the intent was to relax the same-browser check, the option name is wrong and the line is a no-op; if the strategy version does accept it, disabling session validation weakens the magic-link flow. Worth confirming against the installed package's typings.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

},
async ({
email,
Expand Down
6 changes: 5 additions & 1 deletion apps/webapp/app/services/runsReplicationInstance.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ function initializeRunsReplicationInstance() {

console.log("🗃️ Runs replication service enabled");

const url = new URL(env.RUN_REPLICATION_CLICKHOUSE_URL);
// Remove secure param to prevent Unknown URL parameters error
url.searchParams.delete("secure");

const clickhouse = new ClickHouse({
url: env.RUN_REPLICATION_CLICKHOUSE_URL,
url: url.toString(),
name: "runs-replication",
keepAlive: {
enabled: env.RUN_REPLICATION_KEEP_ALIVE_ENABLED === "1",
Expand Down
12 changes: 9 additions & 3 deletions internal-packages/clickhouse/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
FROM golang


RUN go install github.com/pressly/goose/v3/cmd/goose@latest


WORKDIR /app
COPY ./schema ./schema
COPY ./cmd ./cmd
COPY ./migrate.sh ./migrate.sh

RUN go build -o /usr/local/bin/transform ./cmd/transform/main.go
RUN chmod +x ./migrate.sh

ENV GOOSE_DRIVER=clickhouse
ENV GOOSE_DBSTRING="tcp://default:password@clickhouse:9000"
ENV GOOSE_MIGRATION_DIR=./schema
CMD ["goose", "up"]

ENTRYPOINT ["./migrate.sh"]
Comment on lines +5 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 ClickHouse migration container fails to build because it copies files that don't exist

The migration image build copies a helper script and a source folder (COPY ./cmd ./cmd / COPY ./migrate.sh ./migrate.sh at internal-packages/clickhouse/Dockerfile:7-8) that are not present in the package, so the image can never be built.
Impact: Anyone starting local services or CI that builds the ClickHouse migration image gets a hard build failure and no migrations run.

Missing files referenced by the Dockerfile

A directory listing of internal-packages/clickhouse/ shows only Dockerfile, README.md, package.json, schema/, src/, tsconfigs and vitest.config.ts. There is no cmd/transform/main.go and no migrate.sh. Therefore:

  • COPY ./cmd ./cmd fails immediately ("/cmd": not found).
  • Even if the COPY were skipped, RUN go build -o /usr/local/bin/transform ./cmd/transform/main.go and ENTRYPOINT ["./migrate.sh"] (internal-packages/clickhouse/Dockerfile:10,17) would fail.

The previous CMD ["goose", "up"] worked with just the schema directory.

Prompt for agents
The Dockerfile in internal-packages/clickhouse now depends on a `cmd/transform/main.go` Go program and a `migrate.sh` entrypoint script, but neither file exists in the package. The image build will fail at the COPY steps. Either add these files to the package (the transform program and the migrate.sh wrapper that runs goose with up/down arguments), or revert the Dockerfile to the previous `CMD ["goose", "up"]` form that only requires the schema directory.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

CMD ["up"]
16 changes: 10 additions & 6 deletions packages/build/src/extensions/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,22 +317,26 @@ class PlaywrightExtension implements BuildExtension {

Array.from(browsersToInstall).forEach((browser) => {
instructions.push(
`RUN grep -A5 -m1 "browser: ${browser}" /tmp/browser-info.txt > /tmp/${browser}-info.txt`,
// Extract the block for the specific browser.
// We look for a line starting with "browser: {browser}" OR "{browser} v" (legacy)
// Then we collect lines until the next block starts (line starting with browser: or certain chars) or an empty line.
`RUN awk '/^browser: ${browser}|^${browser} v/{flag=1; print; next} /^(browser:|[a-z-]+ v)/{flag=0} flag' /tmp/browser-info.txt > /tmp/${browser}-info.txt`,

`RUN INSTALL_DIR=$(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
`RUN INSTALL_DIR=$(grep -i "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
DIR_NAME=$(basename "$INSTALL_DIR") && \
if [ -z "$DIR_NAME" ]; then echo "Failed to extract installation directory for ${browser}"; exit 1; fi && \
if [ -z "$DIR_NAME" ]; then echo "Failed to extract installation directory for ${browser}. Content of /tmp/${browser}-info.txt:"; cat /tmp/${browser}-info.txt; exit 1; fi && \
MS_DIR="/ms-playwright/$DIR_NAME" && \
mkdir -p "$MS_DIR"`,

`RUN DOWNLOAD_URL=$(grep "Download url:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs | sed "s/mac-arm64/linux/g" | sed "s/mac-15-arm64/ubuntu-20.04/g") && \
`RUN DOWNLOAD_URL=$(grep -i "Download url:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs | sed "s/mac-arm64/linux/g" | sed "s/mac-15-arm64/ubuntu-20.04/g") && \
if [ -z "$DOWNLOAD_URL" ]; then echo "Failed to extract download URL for ${browser}"; exit 1; fi && \
echo "Downloading ${browser} from $DOWNLOAD_URL" && \
curl -L -o /tmp/${browser}.zip "$DOWNLOAD_URL" && \
if [ $? -ne 0 ]; then echo "Failed to download ${browser}"; exit 1; fi && \
unzip -q /tmp/${browser}.zip -d "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
INSTALL_LOCATION=$(grep -i "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
unzip -q /tmp/${browser}.zip -d "/ms-playwright/$(basename "$INSTALL_LOCATION")" && \
if [ $? -ne 0 ]; then echo "Failed to extract ${browser}"; exit 1; fi && \
chmod -R +x "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
chmod -R +x "/ms-playwright/$(basename "$INSTALL_LOCATION")" && \
rm /tmp/${browser}.zip`
);
});
Expand Down
89 changes: 49 additions & 40 deletions packages/cli-v3/src/deploy/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,47 +486,57 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
};
}

const [credentialsError, credentials] = await tryCatch(
getDockerUsernameAndPassword(apiClient, deploymentId)
);
let credentials;
if (cloudRegistryHost.endsWith("amazonaws.com")) {
const [credentialsError, result] = await tryCatch(
getDockerUsernameAndPassword(apiClient, deploymentId)
);

if (credentialsError) {
return {
ok: false as const,
error: `Failed to get docker credentials: ${credentialsError.message}`,
logs: "",
};
if (credentialsError) {
return {
ok: false as const,
error: `Failed to get docker credentials: ${credentialsError.message}`,
logs: "",
};
}
credentials = result;
}
Comment on lines +489 to 503

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Docker pushes to non-AWS registries no longer log in, causing deploy failures

Registry credentials are now fetched only when the target registry address ends in amazonaws.com (cloudRegistryHost.endsWith("amazonaws.com") at packages/cli-v3/src/deploy/buildImage.ts:490), so pushes to any other registry silently skip authentication.
Impact: Self-hosted users doing local builds against a non-AWS registry now get "unauthorized" push failures during deploy instead of being logged in automatically.

How the credential path is reached

authenticateToRegistry is set from options.localBuild in packages/cli-v3/src/commands/deploy.ts:441,574, so for every local build that pushes, the CLI previously called getDockerUsernameAndPassword (packages/cli-v3/src/deploy/buildImage.ts:1004-1021), which returns either TRIGGER_DOCKER_USERNAME/TRIGGER_DOCKER_PASSWORD env credentials or credentials generated by the platform via apiClient.generateRegistryCredentials(deploymentId). Those credentials are not AWS-specific — self-hosted installs point TRIGGER_DOCKER_REGISTRY at their own registry.

With the new guard, credentials stays undefined for those hosts and the code takes the else branch that only logs a debug message, so docker buildx build --push runs unauthenticated.

Prompt for agents
In packages/cli-v3/src/deploy/buildImage.ts the registry login is now gated on the registry host ending with `amazonaws.com`. This breaks self-hosted deployments where the platform (or TRIGGER_DOCKER_USERNAME/TRIGGER_DOCKER_PASSWORD) supplies credentials for a non-AWS registry. Consider always attempting to obtain credentials when `authenticateToRegistry` is set and only skipping login when credentials cannot be obtained (e.g. treat a credential fetch failure as 'assume the user is already logged in') rather than keying off the registry hostname.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);
if (credentials) {
logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);

const loginProcess = x(
"docker",
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
{
nodeOptions: {
cwd: options.cwd,
},
}
);
const loginProcess = x(
"docker",
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
{
nodeOptions: {
cwd: options.cwd,
},
}
);

loginProcess.process?.stdin?.write(credentials.password);
loginProcess.process?.stdin?.end();
loginProcess.process?.stdin?.write(credentials.password);
loginProcess.process?.stdin?.end();

for await (const line of loginProcess) {
errors.push(line);
logger.debug(line);
}
for await (const line of loginProcess) {
errors.push(line);
logger.debug(line);
}

if (loginProcess.exitCode !== 0) {
return {
ok: false as const,
error: `Failed to login to registry: ${cloudRegistryHost}`,
logs: extractLogs(errors),
};
}
if (loginProcess.exitCode !== 0) {
return {
ok: false as const,
error: `Failed to login to registry: ${cloudRegistryHost}`,
logs: extractLogs(errors),
};
}

options.onLog?.(`Successfully logged in to the remote registry`);
options.onLog?.(`Successfully logged in to the remote registry`);
} else {
logger.debug(
`Skipping automatic registry login for ${cloudRegistryHost}. Please ensure you are logged in locally.`
);
}
}

const projectCacheRef = getProjectCacheRefFromImageTag(imageTag);
Expand All @@ -550,13 +560,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
options.noCache ? "--no-cache" : undefined,
...(useRegistryCache
? [
"--cache-to",
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${
cacheCompression === "zstd" ? ",compression=zstd" : ""
}`,
"--cache-from",
`type=registry,ref=${projectCacheRef}`,
]
"--cache-to",
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${cacheCompression === "zstd" ? ",compression=zstd" : ""
}`,
"--cache-from",
`type=registry,ref=${projectCacheRef}`,
]
: []),
"--output",
outputOptions.join(","),
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/v3/apiClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,20 +1366,28 @@ export class ApiClient {
});
}

async appendToStream<TBody extends BodyInit>(
async appendToStream<TBody>(
runId: string,
target: string,
streamId: string,
part: TBody,
requestOptions?: ZodFetchOptions
) {
// Serialize object payloads to JSON to prevent [object Object] coercion by fetch
const body =
typeof part === "string" || part instanceof ArrayBuffer || part instanceof Blob ||
part instanceof FormData || part instanceof URLSearchParams ||
(typeof ReadableStream !== "undefined" && part instanceof ReadableStream)
? (part as BodyInit)
: JSON.stringify(part);

return zodfetch(
AppendToStreamResponseBody,
`${this.baseUrl}/realtime/v1/streams/${runId}/${target}/${streamId}/append`,
{
method: "POST",
headers: this.#getHeaders(false),
body: part,
body,
},
mergeRequestOptions(this.defaultRequestOptions, requestOptions)
);
Expand Down
Loading