From 8ec252059a07577f31c750cd1b90867a7e7975b9 Mon Sep 17 00:00:00 2001 From: luren Date: Fri, 29 May 2026 18:29:32 +0800 Subject: [PATCH] fix(cli): show migration failure reason --- .changeset/show-migration-failure-reason.md | 5 ++ .../src/migration/migration-screen.ts | 51 +++++++++++++++++-- .../test/migration/migration-screen.test.ts | 6 ++- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 .changeset/show-migration-failure-reason.md diff --git a/.changeset/show-migration-failure-reason.md b/.changeset/show-migration-failure-reason.md new file mode 100644 index 00000000..2a09c1b5 --- /dev/null +++ b/.changeset/show-migration-failure-reason.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Show the underlying error when migration fails. diff --git a/apps/kimi-code/src/migration/migration-screen.ts b/apps/kimi-code/src/migration/migration-screen.ts index a0800d84..6d1f89e1 100644 --- a/apps/kimi-code/src/migration/migration-screen.ts +++ b/apps/kimi-code/src/migration/migration-screen.ts @@ -93,6 +93,7 @@ export class MigrationScreenComponent extends Container implements Focusable { private spinnerTimer: ReturnType | undefined; private report: MigrationReport | undefined; private migrationFailed = false; + private migrationFailureReason: string | undefined; constructor(opts: MigrationScreenOptions) { super(); @@ -113,8 +114,9 @@ export class MigrationScreenComponent extends Container implements Focusable { } /** Host calls this if runMigration threw. */ - showFailure(): void { + showFailure(error?: unknown): void { this.migrationFailed = true; + this.migrationFailureReason = formatMigrationFailureReason(error); this.phase = 'result'; this.stopSpinner(); } @@ -260,8 +262,8 @@ export class MigrationScreenComponent extends Container implements Focusable { this.showResult(report); this.opts.requestRender?.(); }, - () => { - this.showFailure(); + (error) => { + this.showFailure(error); this.opts.requestRender?.(); }, ); @@ -280,6 +282,10 @@ export class MigrationScreenComponent extends Container implements Focusable { const lines: string[] = [chalk.hex(colors.primary)('─'.repeat(width))]; if (this.migrationFailed) { lines.push(chalk.hex(colors.error).bold(' Migration failed')); + if (this.migrationFailureReason !== undefined) { + lines.push(''); + lines.push(chalk.hex(colors.text)(` Reason: ${this.migrationFailureReason}`)); + } lines.push(''); lines.push(chalk.hex(colors.text)(' You can retry later by running "kimi migrate".')); lines.push(''); @@ -487,6 +493,45 @@ export class MigrationScreenComponent extends Container implements Focusable { } } +function formatMigrationFailureReason(error: unknown): string | undefined { + let reason: string | undefined; + if (error instanceof Error) { + reason = error.message !== '' ? error.message : error.name; + } else if (typeof error === 'string') { + reason = error; + } else if (typeof error === 'object' && error !== null) { + const maybeMessage = (error as { readonly message?: unknown }).message; + if (typeof maybeMessage === 'string' && maybeMessage !== '') { + reason = maybeMessage; + } + } + if (reason === undefined) { + switch (typeof error) { + case 'number': + case 'boolean': + case 'bigint': + reason = `${error}`; + break; + case 'symbol': + reason = + error.description !== undefined ? `Symbol(${error.description})` : 'Symbol rejection'; + break; + case 'function': + reason = error.name !== '' ? `Function ${error.name}` : 'Function rejection'; + break; + case 'object': + if (error !== null) reason = 'Object rejection'; + break; + case 'undefined': + break; + case 'string': + break; + } + } + const trimmed = reason?.trim(); + return trimmed === undefined || trimmed === '' ? undefined : trimmed; +} + function summarizePlan(plan: MigrationPlan): string { const parts: string[] = []; if (plan.totalSessions > 0) parts.push(`${plan.totalSessions} sessions`); diff --git a/apps/kimi-code/test/migration/migration-screen.test.ts b/apps/kimi-code/test/migration/migration-screen.test.ts index 240cea9a..f450b099 100644 --- a/apps/kimi-code/test/migration/migration-screen.test.ts +++ b/apps/kimi-code/test/migration/migration-screen.test.ts @@ -578,7 +578,7 @@ describe('MigrationScreenComponent — execution wiring', () => { expect(onCompleteResult?.migrated).toBe(true); }); - it('lands on the failure screen when the runner rejects', async () => { + it('lands on the failure screen with the runner rejection reason', async () => { const c = new MigrationScreenComponent({ plan: makePlan(), sourceHome: '/x/.kimi', @@ -592,6 +592,8 @@ describe('MigrationScreenComponent — execution wiring', () => { c.handleInput('\r'); // ask1: Migrate now c.handleInput('\r'); // ask2: Config only -> begins migration await new Promise((res) => setTimeout(res, 0)); - expect(c.render(80).join('\n')).toContain('Migration failed'); + const out = c.render(80).join('\n'); + expect(out).toContain('Migration failed'); + expect(out).toContain('Reason: boom'); }); });