@linzumi/cli 1.0.80 → 1.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +386 -382
- package/package.json +3 -2
- package/scripts/diagnose-claude-probe.ts +198 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linzumi/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.82",
|
|
4
4
|
"description": "Linzumi CLI \u2014 point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"replay:agent-backend": "tsx scripts/replay-agent-backend.ts",
|
|
29
29
|
"vendor:codex-app-server-protocol:check": "tsx scripts/vendor-codex-app-server-protocol.ts --check",
|
|
30
30
|
"replay:agent-backend-browser": "tsx scripts/replay-agent-backend-browser.ts",
|
|
31
|
-
"promote:gstack-claude-smoke-fixture": "tsx scripts/promote-gstack-claude-smoke-fixture.ts"
|
|
31
|
+
"promote:gstack-claude-smoke-fixture": "tsx scripts/promote-gstack-claude-smoke-fixture.ts",
|
|
32
|
+
"diagnose:claude-probe": "tsx scripts/diagnose-claude-probe.ts"
|
|
32
33
|
},
|
|
33
34
|
"keywords": [
|
|
34
35
|
"linzumi",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/*
|
|
2
|
+
- Date: 2026-07-11
|
|
3
|
+
Spec: audit PR #3019, W17 (William seq 870: "SDK startup 15s timeout x2"
|
|
4
|
+
while `claude` ran fine when invoked directly).
|
|
5
|
+
Relationship: Side-by-side diagnostic for the Claude SDK startup probe.
|
|
6
|
+
Runs (1) a DIRECT invocation of the exact `claude` executable the probe's
|
|
7
|
+
fallback scan resolves and (2) the real availability probe
|
|
8
|
+
(probeClaudeCodeAvailabilityStatus - the same code path the runner runs at
|
|
9
|
+
startup), with IDENTICAL environment preparation
|
|
10
|
+
(prepareClaudeCodeSpawnEnvironment) and binary resolution, capturing
|
|
11
|
+
stderr, exit codes, and timing for both. When the probe fails while the
|
|
12
|
+
direct invocation succeeds, the report shows the probe's captured child
|
|
13
|
+
stderr tail - the evidence that used to be discarded (the SDK spawns with
|
|
14
|
+
stdio 'ignore' for stderr unless a callback is passed).
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
npx tsx scripts/diagnose-claude-probe.ts [--claude-bin /path/to/claude]
|
|
18
|
+
|
|
19
|
+
Exit code 0 always (this is a reporter, not a gate); read the verdict.
|
|
20
|
+
*/
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import {
|
|
25
|
+
findClaudeCodeExecutableFallback,
|
|
26
|
+
prepareClaudeCodeSpawnEnvironment,
|
|
27
|
+
probeClaudeCodeAvailabilityStatus,
|
|
28
|
+
} from '../src/claudeCodeSession';
|
|
29
|
+
|
|
30
|
+
const DIRECT_TIMEOUT_MS = 15_000; // mirrors the probe's initializeTimeoutMs
|
|
31
|
+
|
|
32
|
+
type DirectResult = {
|
|
33
|
+
readonly command: string;
|
|
34
|
+
readonly durationMs: number;
|
|
35
|
+
readonly exitCode: number | null;
|
|
36
|
+
readonly timedOut: boolean;
|
|
37
|
+
readonly stdoutTail: string;
|
|
38
|
+
readonly stderrTail: string;
|
|
39
|
+
readonly spawnError?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function tail(value: string, max = 600): string {
|
|
43
|
+
const collapsed = value.replace(/\s+/g, ' ').trim();
|
|
44
|
+
return collapsed.slice(-max);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function runDirect(
|
|
48
|
+
executable: string,
|
|
49
|
+
args: readonly string[],
|
|
50
|
+
env: NodeJS.ProcessEnv
|
|
51
|
+
): Promise<DirectResult> {
|
|
52
|
+
const started = Date.now();
|
|
53
|
+
return await new Promise((resolvePromise) => {
|
|
54
|
+
let stdout = '';
|
|
55
|
+
let stderr = '';
|
|
56
|
+
let settled = false;
|
|
57
|
+
const child = spawn(executable, [...args], { env, windowsHide: true });
|
|
58
|
+
const settle = (partial: Partial<DirectResult>): void => {
|
|
59
|
+
if (settled) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
settled = true;
|
|
63
|
+
clearTimeout(deadline);
|
|
64
|
+
resolvePromise({
|
|
65
|
+
command: `${executable} ${args.join(' ')}`,
|
|
66
|
+
durationMs: Date.now() - started,
|
|
67
|
+
exitCode: child.exitCode,
|
|
68
|
+
timedOut: false,
|
|
69
|
+
stdoutTail: tail(stdout),
|
|
70
|
+
stderrTail: tail(stderr),
|
|
71
|
+
...partial,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const deadline = setTimeout(() => {
|
|
75
|
+
child.kill('SIGKILL');
|
|
76
|
+
settle({ timedOut: true, exitCode: null });
|
|
77
|
+
}, DIRECT_TIMEOUT_MS);
|
|
78
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
79
|
+
stdout += chunk.toString();
|
|
80
|
+
});
|
|
81
|
+
child.stderr?.on('data', (chunk: Buffer) => {
|
|
82
|
+
stderr += chunk.toString();
|
|
83
|
+
});
|
|
84
|
+
child.on('error', (error) => {
|
|
85
|
+
settle({ spawnError: error.message });
|
|
86
|
+
});
|
|
87
|
+
child.on('close', (code) => {
|
|
88
|
+
settle({ exitCode: code });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function main(): Promise<void> {
|
|
94
|
+
const claudeBinFlagIndex = process.argv.indexOf('--claude-bin');
|
|
95
|
+
const claudeBin =
|
|
96
|
+
claudeBinFlagIndex === -1
|
|
97
|
+
? undefined
|
|
98
|
+
: process.argv[claudeBinFlagIndex + 1];
|
|
99
|
+
// A bare `--claude-bin` with no path must fail loudly, not silently run
|
|
100
|
+
// as if the flag was never given (Greptile P2, PR #3035).
|
|
101
|
+
if (claudeBinFlagIndex !== -1 && claudeBin === undefined) {
|
|
102
|
+
console.error('--claude-bin requires a path argument');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// IDENTICAL env preparation to both the probe and real sessions: the one
|
|
107
|
+
// shared function (merged login-shell PATH).
|
|
108
|
+
const env = prepareClaudeCodeSpawnEnvironment();
|
|
109
|
+
|
|
110
|
+
// IDENTICAL binary resolution to the probe's fallback scan.
|
|
111
|
+
const resolvedFallback = findClaudeCodeExecutableFallback({
|
|
112
|
+
env,
|
|
113
|
+
platform: process.platform,
|
|
114
|
+
fileExists: existsSync,
|
|
115
|
+
homeDir: homedir(),
|
|
116
|
+
});
|
|
117
|
+
const directExecutable = claudeBin ?? resolvedFallback;
|
|
118
|
+
|
|
119
|
+
console.log('=== W17 claude probe side-by-side diagnostic ===');
|
|
120
|
+
console.log(`cwd: ${process.cwd()}`);
|
|
121
|
+
console.log(
|
|
122
|
+
`resolved user-installed claude: ${resolvedFallback ?? '(none found on PATH or in ~/.local/bin, ~/.claude/local)'}`
|
|
123
|
+
);
|
|
124
|
+
console.log(`explicit --claude-bin: ${claudeBin ?? '(not set)'}`);
|
|
125
|
+
console.log('');
|
|
126
|
+
|
|
127
|
+
// --- Side A: direct invocation of the same binary ---
|
|
128
|
+
console.log('--- A: direct invocation ---');
|
|
129
|
+
let direct: DirectResult | undefined;
|
|
130
|
+
if (directExecutable === undefined) {
|
|
131
|
+
console.log('skipped: no claude executable resolved for a direct run');
|
|
132
|
+
} else {
|
|
133
|
+
direct = await runDirect(directExecutable, ['--version'], env);
|
|
134
|
+
console.log(
|
|
135
|
+
`direct \`${direct.command}\`: exit=${direct.exitCode} timedOut=${direct.timedOut} in ${direct.durationMs}ms`
|
|
136
|
+
);
|
|
137
|
+
if (direct.stdoutTail !== '') {
|
|
138
|
+
console.log(` stdout: ${direct.stdoutTail}`);
|
|
139
|
+
}
|
|
140
|
+
if (direct.stderrTail !== '') {
|
|
141
|
+
console.log(` stderr: ${direct.stderrTail}`);
|
|
142
|
+
}
|
|
143
|
+
if (direct.spawnError !== undefined) {
|
|
144
|
+
console.log(` spawn error: ${direct.spawnError}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log('');
|
|
148
|
+
|
|
149
|
+
// --- Side B: the real SDK startup probe (same code path as the runner) ---
|
|
150
|
+
console.log(
|
|
151
|
+
'--- B: SDK startup probe (probeClaudeCodeAvailabilityStatus) ---'
|
|
152
|
+
);
|
|
153
|
+
const started = Date.now();
|
|
154
|
+
const outcome = await probeClaudeCodeAvailabilityStatus({
|
|
155
|
+
cwd: process.cwd(),
|
|
156
|
+
claudeBin,
|
|
157
|
+
});
|
|
158
|
+
const probeMs = Date.now() - started;
|
|
159
|
+
console.log(
|
|
160
|
+
`probe verdict: status=${outcome.status} transient=${outcome.transient} in ${probeMs}ms`
|
|
161
|
+
);
|
|
162
|
+
if (outcome.detail !== undefined) {
|
|
163
|
+
console.log(`probe failure detail (SDK error + child stderr tail):`);
|
|
164
|
+
console.log(` ${outcome.detail}`);
|
|
165
|
+
}
|
|
166
|
+
console.log('');
|
|
167
|
+
|
|
168
|
+
console.log('--- verdict ---');
|
|
169
|
+
// The verdict branches on side A's ACTUAL result: "both failed" is a
|
|
170
|
+
// uniformly broken setup, not a probe-vs-direct divergence (Greptile P2,
|
|
171
|
+
// PR #3035).
|
|
172
|
+
const directSucceeded =
|
|
173
|
+
direct !== undefined &&
|
|
174
|
+
direct.exitCode === 0 &&
|
|
175
|
+
!direct.timedOut &&
|
|
176
|
+
direct.spawnError === undefined;
|
|
177
|
+
if (outcome.status === 'logged_in') {
|
|
178
|
+
console.log(
|
|
179
|
+
'probe and direct invocation agree: Claude Code is launchable.'
|
|
180
|
+
);
|
|
181
|
+
} else if (directSucceeded) {
|
|
182
|
+
console.log(
|
|
183
|
+
'DIVERGENCE: the direct invocation succeeded but the probe failed. The failure detail above is the evidence to attach to the issue.'
|
|
184
|
+
);
|
|
185
|
+
} else if (direct !== undefined) {
|
|
186
|
+
console.log(
|
|
187
|
+
'both the direct invocation AND the probe failed - a uniformly broken claude setup on this machine, not a probe divergence. Fix the direct invocation first (its stderr/exit code above).'
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
console.log(
|
|
191
|
+
'no user-installed claude and the probe failed - see detail above.'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
// Reporter, not a gate.
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void main();
|