Skip to content

Commit cd54a1c

Browse files
committed
fix: use non interactive context in sub shell (#2632)
Synced from monorepo@003eb27457e332d49dbb15612bc8ddd26741688c
1 parent 918436a commit cd54a1c

4 files changed

Lines changed: 58 additions & 8 deletions

File tree

.sync-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dd3da607cce506b5497c04ac07c44774e7954892
1+
003eb27457e332d49dbb15612bc8ddd26741688c

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
"zod": "4.4.3",
3838
"zod-config": "1.4.0",
3939
"@xata.io/ai": "0.1.0",
40-
"@xata.io/api": "0.1.1",
4140
"@xata.io/config": "0.0.1",
4241
"@xata.io/lang": "0.0.1",
4342
"@xata.io/pgroll": "0.9.0",
4443
"@xata.io/pgstream": "0.2.0",
4544
"@xata.io/sql": "0.1.4",
4645
"@xata.io/test-utils": "0.0.1",
4746
"@xata.io/tsconfig": "0.0.1",
48-
"@xata.io/utils": "0.1.0"
47+
"@xata.io/utils": "0.1.0",
48+
"@xata.io/api": "0.1.1"
4949
},
5050
"scripts": {
5151
"dev": "bun src/bin/cli.ts",

src/context.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ export interface LocalContext extends CommandContext, StricliAutoCompleteContext
3232
/**
3333
* Whether the CLI is running in an interactive context.
3434
*
35-
* False when running in CI or when invoked from an agentic context
36-
* (Cursor, Claude Code, Amp, etc.). Prompt helpers short-circuit when
37-
* this is false.
35+
* False when stdio is not attached to an interactive terminal, when running
36+
* in CI, or when invoked from an agentic context (Cursor, Claude Code, Amp,
37+
* etc.). Prompt helpers short-circuit when this is false.
3838
*/
3939
readonly isInteractive: boolean;
4040
readonly print: typeof print;
@@ -63,6 +63,10 @@ type BuildContextOptions = {
6363
cliInvocationId?: string;
6464
};
6565

66+
export function getIsInteractive(process: NodeJS.Process, { isCI, isAgent }: { isCI: boolean; isAgent: boolean }) {
67+
return process.stdin.isTTY === true && process.stdout.isTTY === true && !(isCI || isAgent);
68+
}
69+
6670
export async function buildContext(process: NodeJS.Process, options: BuildContextOptions = {}): Promise<LocalContext> {
6771
const debug = Boolean(Bun.env.DEBUG);
6872
const usingEnvApiKey = Boolean(env.XATA_API_KEY);
@@ -81,8 +85,8 @@ export async function buildContext(process: NodeJS.Process, options: BuildContex
8185
os,
8286
fs,
8387
path,
84-
// Treat both CI and agentic invocations as non-interactive
85-
isInteractive: !(ciInfo.isCI || agent.isAgent),
88+
// Treat captured/piped output, CI, and agentic invocations as non-interactive.
89+
isInteractive: getIsInteractive(process, { isCI: ciInfo.isCI, isAgent: agent.isAgent }),
8690
print,
8791
getActiveProfile,
8892
getOrganization,

src/context.unit.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, test } from 'bun:test';
2+
import { getIsInteractive } from './context';
3+
4+
function buildProcess({ stdinIsTTY, stdoutIsTTY }: { stdinIsTTY: boolean; stdoutIsTTY: boolean }) {
5+
return {
6+
stdin: { isTTY: stdinIsTTY },
7+
stdout: { isTTY: stdoutIsTTY },
8+
stderr: { write: () => {} }
9+
} as unknown as NodeJS.Process;
10+
}
11+
12+
describe('buildContext', () => {
13+
test('is interactive when stdin and stdout are TTYs', async () => {
14+
const isInteractive = getIsInteractive(buildProcess({ stdinIsTTY: true, stdoutIsTTY: true }), {
15+
isCI: false,
16+
isAgent: false
17+
});
18+
19+
expect(isInteractive).toBe(true);
20+
});
21+
22+
test('is non-interactive when stdout is captured', async () => {
23+
const isInteractive = getIsInteractive(buildProcess({ stdinIsTTY: true, stdoutIsTTY: false }), {
24+
isCI: false,
25+
isAgent: false
26+
});
27+
28+
expect(isInteractive).toBe(false);
29+
});
30+
31+
test('is non-interactive when stdin is not a TTY', async () => {
32+
const isInteractive = getIsInteractive(buildProcess({ stdinIsTTY: false, stdoutIsTTY: true }), {
33+
isCI: false,
34+
isAgent: false
35+
});
36+
37+
expect(isInteractive).toBe(false);
38+
});
39+
40+
test('is non-interactive in CI or agentic contexts', async () => {
41+
const process = buildProcess({ stdinIsTTY: true, stdoutIsTTY: true });
42+
43+
expect(getIsInteractive(process, { isCI: true, isAgent: false })).toBe(false);
44+
expect(getIsInteractive(process, { isCI: false, isAgent: true })).toBe(false);
45+
});
46+
});

0 commit comments

Comments
 (0)