From 7429732389193ca316997f546948c9adfc1e832a Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Wed, 17 Jun 2026 12:07:10 +0200 Subject: [PATCH] software-factory: richer workspace-sync failure logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a failed sync, emit a structured, attributed trace — the pushed/pulled/ deleted/conflict counts, the realm-server JSON:API error payload, and any skipped conflicting paths — instead of "see prior log lines". This leaves a usable record on the factory's own log channel without scraping the CLI's interleaved progress output, and it's always on (no flag), so it works whether or not anyone anticipated the failure. Replaces the forensic intent of the dropped --halt-on-sync-error flag without sacrificing pull-recovery. CS-11575 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/factory-issue-loop-wiring.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/software-factory/src/factory-issue-loop-wiring.ts b/packages/software-factory/src/factory-issue-loop-wiring.ts index 37ca25db0c1..a34bb22ae49 100644 --- a/packages/software-factory/src/factory-issue-loop-wiring.ts +++ b/packages/software-factory/src/factory-issue-loop-wiring.ts @@ -414,16 +414,35 @@ export async function syncWorkspaceToRealm( let result = await withStdoutRedirected(() => client.sync(targetRealm, workspaceDir, { preferLocal: true }), ); - if (result.error) { - log.warn(`Workspace sync error: ${result.error}`); - return { ok: false, error: result.error }; - } - if (result.hasError) { - log.warn('Workspace sync completed with errors — see prior log lines'); + if (result.error || result.hasError) { + // Emit a structured, attributed failure trace so a sync error leaves a + // usable record on its own — instead of "see prior log lines", which + // means scraping the CLI's interleaved progress output after the fact. + let counts = + `pushed=${result.pushed.length}, pulled=${result.pulled.length}, ` + + `remoteDeleted=${result.remoteDeleted.length}, ` + + `localDeleted=${result.localDeleted.length}, ` + + `skippedConflicts=${result.skippedConflicts.length}`; + log.warn(`Workspace sync FAILED — ${counts}`); + if (result.error) { + // The realm-server's JSON:API error payload (e.g. the 500 "Write + // Error" body), not just the HTTP status line. + log.warn(` realm-server response: ${result.error}`); + } + if (result.skippedConflicts.length > 0) { + log.warn( + ` skipped (conflicting) paths: ${result.skippedConflicts.join(', ')}`, + ); + } + // Note: the attempted-batch manifest isn't in SyncResult — on a fatal + // atomic upload nothing lands in `pushed`. The attempted paths appear in + // the CLI progress lines (routed to stderr above); surfacing them in the + // result itself is a boxel-cli follow-up. return { ok: false, error: - 'Workspace sync completed with per-file errors — see prior log lines for the failing paths and the realm-server response.', + result.error ?? + `Workspace sync completed with per-file errors (${counts}).`, }; } return { ok: true };