From 5cd83bf3bee06550081fbebababaee6780c3e4af Mon Sep 17 00:00:00 2001 From: Ricky Schema Cascade Date: Wed, 16 Sep 2026 22:01:50 -0700 Subject: [PATCH] fix(cli): show the real cause in the deployments-list error snippet, not the boilerplate head cloud#3649 fixed the backend to keep both the head and tail of a long, unrecognized deployment-run failure (stored as ...[N chars omitted]...), so the real cause survives its own 10_000-char truncation instead of being lost behind the unconditional mount/smithy bootstrap boilerplate. But formatDeploymentErrorSnippet in `deployments list`'s compact table still sliced from position 0 down to 240 chars - well inside the boilerplate head, before ever reaching the elision marker. Every unrecognized failure kept showing an opaque [smithy-diag] dump in the table view even after the backend fix landed (confirmed live: the stored `error` for chief-watchdog/hn-monitor already contains the elision marker and the real tail content via --json, but the compact table still showed only the head). Fix: when the marker is present, snippet from just after it instead of from the start. Co-Authored-By: Claude Sonnet 5 --- packages/cli/src/list-command.test.ts | 28 +++++++++++++++++++++++++++ packages/cli/src/list-command.ts | 17 ++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/cli/src/list-command.test.ts b/packages/cli/src/list-command.test.ts index 9936910a..bf7ab16b 100644 --- a/packages/cli/src/list-command.test.ts +++ b/packages/cli/src/list-command.test.ts @@ -358,6 +358,34 @@ test('formatDeploymentsTable collapses and bounds multiline error details', () = assert.match(out, /\.\.\./); }); +test('formatDeploymentsTable snippets past an elision marker instead of repeating boilerplate', () => { + // cloud's deployment-run-failure-class.ts stores unrecognized long failures + // as `...[N chars omitted]...` so the real cause + // survives its own 10_000-char truncation. Naively slicing from position 0 + // here would show only the boilerplate every time and never reach it. + const boilerplate = '[smithy-diag] node=v25.6.0 '.repeat(20); + const realCause = 'unexpected teardown error: mount flush wedged past the deadline'; + const longError = `${boilerplate}...[9153 chars omitted]...${realCause}`; + const out = formatDeploymentsTable([ + { + agentId: 'agent-elided-error', + personaId: 'elided-error', + personaSlug: 'elided-error', + deployedName: 'elided-error', + status: 'active', + createdAt: '2026-09-17T00:00:00.000Z', + lastUsedAt: null, + lastRunStatus: 'failed', + lastError: longError, + scheduleIds: [], + deployedByUserId: 'user-1' + } + ]); + + assert.match(out, /elided-error: unexpected teardown error: mount flush wedged past the deadline/); + assert.doesNotMatch(out, /smithy-diag/); +}); + test('parseDeploymentLogsArgs accepts selector and log flags', () => { assert.deepEqual( parseDeploymentLogsArgs([ diff --git a/packages/cli/src/list-command.ts b/packages/cli/src/list-command.ts index a96cb8d9..4b224989 100644 --- a/packages/cli/src/list-command.ts +++ b/packages/cli/src/list-command.ts @@ -591,12 +591,29 @@ function readFiniteNumber(record: Record, key: string): number const DEPLOYMENT_ERROR_SNIPPET_LENGTH = 240; +// cloud's deployment-run-failure-class.ts marks an unrecognized long failure +// as "...[N chars omitted]..." (head = boilerplate mount/smithy +// bootstrap that runs before every persona invocation, tail = whatever +// actually happened). Truncating from position 0 below would always show the +// boilerplate and never reach the tail where the real cause lives, so when +// this marker is present, snippet from just after it instead. +const OMITTED_MARKER_PATTERN = /\.\.\.\[\d+ chars omitted\]\.\.\./; + function formatDeploymentErrorSnippet(error: string): string { const normalized = error .replace(/[\u0000-\u001f\u007f]+/g, ' ') .replace(/\s+/g, ' ') .trim(); if (normalized.length <= DEPLOYMENT_ERROR_SNIPPET_LENGTH) return normalized; + const marker = normalized.match(OMITTED_MARKER_PATTERN); + if (marker && marker.index !== undefined) { + const afterMarker = normalized.slice(marker.index + marker[0].length).trim(); + if (afterMarker.length > 0) { + return afterMarker.length <= DEPLOYMENT_ERROR_SNIPPET_LENGTH + ? afterMarker + : `${afterMarker.slice(0, DEPLOYMENT_ERROR_SNIPPET_LENGTH - 3)}...`; + } + } return `${normalized.slice(0, DEPLOYMENT_ERROR_SNIPPET_LENGTH - 3)}...`; }