Fix/openclaude diagnostics settings (#483)

* fix: use openclaude paths in diagnostics and settings

* fix: strip leaked reasoning from assistant output

* fix: preserve legacy claude config compatibility

* fix: tighten path and reasoning compatibility

* fix: buffer streamed reasoning leak preambles

* test: cover openclaude migration and reasoning fixes

* test: isolate execFileNoThrow from cross-file mocks
This commit is contained in:
Kevin Codex
2026-04-09 20:42:51 +08:00
committed by GitHub
parent 32fbd0c7b4
commit 42b121bd0d
23 changed files with 934 additions and 101 deletions

View File

@@ -3,6 +3,7 @@
*/
import { access, chmod, writeFile } from 'fs/promises'
import { homedir } from 'os'
import { join } from 'path'
import { type ReleaseChannel, saveGlobalConfig } from './config.js'
import { getClaudeConfigHomeDir } from './envUtils.js'
@@ -19,16 +20,45 @@ import { jsonStringify } from './slowOperations.js'
function getLocalInstallDir(): string {
return join(getClaudeConfigHomeDir(), 'local')
}
function getLegacyLocalInstallDir(homeDir = homedir()): string {
return join(homeDir, '.claude', 'local')
}
export function getCandidateLocalInstallDirs(options?: {
configHomeDir?: string
homeDir?: string
}): string[] {
const homeDir = options?.homeDir ?? homedir()
const configHomeDir = options?.configHomeDir ?? getClaudeConfigHomeDir()
return Array.from(
new Set([join(configHomeDir, 'local'), getLegacyLocalInstallDir(homeDir)]),
)
}
function getCandidateLocalBinaryPaths(localInstallDir: string): string[] {
return [
join(localInstallDir, 'node_modules', '.bin', 'openclaude'),
join(localInstallDir, 'node_modules', '.bin', 'claude'),
]
}
export function isManagedLocalInstallationPath(execPath: string): boolean {
return (
execPath.includes('/.openclaude/local/node_modules/') ||
execPath.includes('/.claude/local/node_modules/')
)
}
export function getLocalClaudePath(): string {
return join(getLocalInstallDir(), 'claude')
return join(getLocalInstallDir(), 'openclaude')
}
/**
* Check if we're running from our managed local installation
*/
export function isRunningFromLocalInstallation(): boolean {
const execPath = process.argv[1] || ''
return execPath.includes('/.claude/local/node_modules/')
return isManagedLocalInstallationPath(process.argv[1] || '')
}
/**
@@ -64,17 +94,17 @@ export async function ensureLocalPackageEnvironment(): Promise<boolean> {
await writeIfMissing(
join(localInstallDir, 'package.json'),
jsonStringify(
{ name: 'claude-local', version: '0.0.1', private: true },
{ name: 'openclaude-local', version: '0.0.1', private: true },
null,
2,
),
)
// Create the wrapper script if it doesn't exist
const wrapperPath = join(localInstallDir, 'claude')
const wrapperPath = getLocalClaudePath()
const created = await writeIfMissing(
wrapperPath,
`#!/bin/sh\nexec "${localInstallDir}/node_modules/.bin/claude" "$@"`,
`#!/bin/sh\nexec "${localInstallDir}/node_modules/.bin/openclaude" "$@"`,
0o755,
)
if (created) {
@@ -142,12 +172,31 @@ export async function installOrUpdateClaudePackage(
* Pure existence probe — callers use this to choose update path / UI hints.
*/
export async function localInstallationExists(): Promise<boolean> {
try {
await access(join(getLocalInstallDir(), 'node_modules', '.bin', 'claude'))
return true
} catch {
return false
for (const localInstallDir of getCandidateLocalInstallDirs()) {
for (const binaryPath of getCandidateLocalBinaryPaths(localInstallDir)) {
try {
await access(binaryPath)
return true
} catch {
// Try next candidate
}
}
}
return false
}
export async function getDetectedLocalInstallDir(): Promise<string | null> {
for (const localInstallDir of getCandidateLocalInstallDirs()) {
for (const binaryPath of getCandidateLocalBinaryPaths(localInstallDir)) {
try {
await access(binaryPath)
return localInstallDir
} catch {
// Try next candidate
}
}
}
return null
}
/**