Skip to content

Commit 58dbafb

Browse files
heskewclaude
andcommitted
fix(upgrade): resolve CONFIRM_DOWNGRADE deterministically when no TTY
An override value the prompt library rejects (its pattern is lowercase yes/no only — YES, true, 1, or a typo all fail) was deleted by the library and fell through to the blocking stdin read, reintroducing the hang the previous commit fixed. With no TTY, resolve the answer without ever reaching prompt.get(): case-insensitive yes/no, and an unrecognized value throws a descriptive error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FNbH9xnNHcd7V1a58ho2CJ
1 parent 358aa01 commit 58dbafb

2 files changed

Lines changed: 30 additions & 9 deletions

File tree

unitTests/upgrade/upgradePrompt.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,21 @@ describe('forceDowngradePrompt — non-interactive starts', () => {
5151
assert.strictEqual(await forceDowngradePrompt(upgradeObj), true);
5252
});
5353

54+
it('accepts the override case-insensitively (the prompt library would reject YES and hang)', async () => {
55+
process.env.CONFIRM_DOWNGRADE = 'YES';
56+
assert.strictEqual(await forceDowngradePrompt(upgradeObj), true);
57+
});
58+
5459
it('declines without a TTY when CONFIRM_DOWNGRADE=no', async () => {
5560
process.env.CONFIRM_DOWNGRADE = 'no';
5661
assert.strictEqual(await forceDowngradePrompt(upgradeObj), false);
5762
});
63+
64+
it('throws on an unrecognized override value instead of falling through to the blocking prompt', async () => {
65+
process.env.CONFIRM_DOWNGRADE = 'true';
66+
await assert.rejects(forceDowngradePrompt(upgradeObj), (error) => {
67+
assert.ok(error.message.includes("Unrecognized CONFIRM_DOWNGRADE value 'true'"));
68+
return true;
69+
});
70+
});
5871
});

upgrade/upgradePrompt.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ const UPGRADE_PROCEED = ['yes', 'y'];
1414
*/
1515
export async function forceDowngradePrompt(upgradeObj: any) {
1616
const override = assignCMDENVVariables(['CONFIRM_DOWNGRADE']);
17-
// Without a terminal the prompt below blocks on stdin forever (systemd, containers, CI), so
18-
// refuse to start instead unless a CONFIRM_DOWNGRADE override answers it (#2046).
19-
if (override.CONFIRM_DOWNGRADE === undefined && !process.stdin.isTTY) {
20-
throw new Error(
21-
`This instance's data was last run on Harper ${upgradeObj.data_version}, which is newer than this installed version ${upgradeObj.upgrade_version}.` +
22-
' Running the older version requires confirmation, and there is no interactive terminal to ask on.' +
23-
' Set CONFIRM_DOWNGRADE=yes (environment variable or --CONFIRM_DOWNGRADE yes) to proceed with the downgrade,' +
24-
` or run Harper ${upgradeObj.data_version} or newer.`
25-
);
17+
// Without a terminal, prompt.get() blocks on stdin forever (systemd, containers, CI) — and an
18+
// override value the prompt library rejects (its pattern is lowercase-only) is deleted and
19+
// falls through to that same blocking read. So with no TTY, resolve the answer here and never
20+
// reach the prompt (#2046).
21+
if (!process.stdin.isTTY) {
22+
if (override.CONFIRM_DOWNGRADE === undefined) {
23+
throw new Error(
24+
`This instance's data was last run on Harper ${upgradeObj.data_version}, which is newer than this installed version ${upgradeObj.upgrade_version}.` +
25+
' Running the older version requires confirmation, and there is no interactive terminal to ask on.' +
26+
' Set CONFIRM_DOWNGRADE=yes (environment variable or --CONFIRM_DOWNGRADE yes) to proceed with the downgrade,' +
27+
` or run Harper ${upgradeObj.data_version} or newer.`
28+
);
29+
}
30+
const answer = override.CONFIRM_DOWNGRADE.toLowerCase();
31+
if (UPGRADE_PROCEED.includes(answer)) return true;
32+
if (answer === 'no' || answer === 'n') return false;
33+
throw new Error(`Unrecognized CONFIRM_DOWNGRADE value '${override.CONFIRM_DOWNGRADE}'; use yes or no.`);
2634
}
2735
let downgradeMessage =
2836
`${os.EOL}` +

0 commit comments

Comments
 (0)