From a109eb6ed98895de4e879058dc4379aee61b352c Mon Sep 17 00:00:00 2001 From: antfleet-ops <285575208+antfleet-ops@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:43:33 +0300 Subject: [PATCH] Expose transaction hashes after confirmation failures Constraint: preserve normal success output while making post-broadcast failures recoverable.\nRejected: surfacing only the backend error | hides the already-broadcast transaction hash.\nConfidence: high\nScope-risk: moderate\nDirective: after an on-chain broadcast, every downstream backend error must keep the tx hash visible.\nTested: npm run typecheck; npm run build\nNot-tested: live backend confirmation failure. --- src/commands/client.ts | 24 +++++++++++++++++++----- src/commands/compute.ts | 20 ++++++++++++++------ src/lib/errors.ts | 9 ++++++++- src/lib/output.ts | 5 +++++ 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/commands/client.ts b/src/commands/client.ts index 7e81938..7bb43ce 100644 --- a/src/commands/client.ts +++ b/src/commands/client.ts @@ -545,11 +545,25 @@ export function registerClientCommands(program: Command): void { ]); const txnHash = Array.isArray(result) ? result[0] : result; - const message = await agentApi.confirmJobFeedback( - chainId, - opts.jobId, - txnHash - ); + let message: string | undefined; + try { + message = await agentApi.confirmJobFeedback( + chainId, + opts.jobId, + txnHash + ); + } catch (err) { + const errMessage = err instanceof Error ? err.message : String(err); + throw new CliError( + `Backend confirmation failed after review transaction was broadcast. Tx hash: ${txnHash}. ${errMessage}`, + "API_ERROR", + "The on-chain transaction was already sent. Keep the Tx Hash and retry or contact support with it.", + { + action: "client review", + txnHash, + } + ); + } if (json) { outputResult(json, { diff --git a/src/commands/compute.ts b/src/commands/compute.ts index 306f7d6..65b0cd0 100644 --- a/src/commands/compute.ts +++ b/src/commands/compute.ts @@ -126,12 +126,20 @@ export function registerComputeCommands(program: Command): void { }); const agentAddress = getWalletAddress(); - const result = await agentApi.computeTopUp( - agentId, - agentAddress, - amount, - txnHash - ); + try { + await agentApi.computeTopUp(agentId, agentAddress, amount, txnHash); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new CliError( + `Backend confirmation failed after compute top-up transaction was broadcast. Tx hash: ${txnHash}. ${message}`, + "API_ERROR", + "The on-chain transaction was already sent. Keep the Tx Hash and retry or contact support with it.", + { + action: "compute top-up", + txnHash, + } + ); + } if (json) { outputResult(json, { diff --git a/src/lib/errors.ts b/src/lib/errors.ts index a1797ba..e7b1e27 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -15,10 +15,17 @@ export type ErrorCode = export class CliError extends Error { code: ErrorCode; recovery?: string; + details?: Record; - constructor(message: string, code: ErrorCode, recovery?: string) { + constructor( + message: string, + code: ErrorCode, + recovery?: string, + details?: Record + ) { super(message); this.code = code; this.recovery = recovery; + this.details = details; } } diff --git a/src/lib/output.ts b/src/lib/output.ts index bb5dfd9..16c4034 100644 --- a/src/lib/output.ts +++ b/src/lib/output.ts @@ -39,6 +39,11 @@ export function outputError( if (isCliErr) { payload.code = errOrMessage.code; if (errOrMessage.recovery) payload.recovery = errOrMessage.recovery; + if (errOrMessage.details) { + for (const [key, value] of Object.entries(errOrMessage.details)) { + payload[key] = String(value); + } + } } process.stdout.write(JSON.stringify(payload) + "\n"); } else {