On Windows, a black console (conhost) window flashed briefly when CodeGraph ran as a background MCP server. Several child spawns were missing `windowsHide: true`, so Windows created a visible console for the child: - scripts/npm-shim.js — launching the bundled runtime (every server start / daemon-idle reconnect) and the self-heal `tar` extraction of a missing platform bundle. - src/reasoning/login.ts — the detached `cmd /c start` browser open. - src/upgrade/index.ts — package-manager spawn (console-attached, so no flash in practice, but set for uniformity: every child spawn now hides). The daemon spawn (#411) and all git execFileSync sites already set it; this closes the remaining gaps. Adds an all-platforms source guard to __tests__/npm-shim.test.ts asserting every spawn in the shim sets windowsHide. Closes #1092 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
00765200d8
commit
6dd5512d8d
@@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- On Windows, a console window no longer briefly flashes when CodeGraph runs as a background MCP server. When the npm launcher started the bundled runtime — which happens every time an editor starts the server or reconnects after the daemon idles out — and during its self-heal step that extracts a missing platform bundle, Windows would pop up a black console (conhost) window for a moment. Both now launch hidden, matching how the daemon already behaved; the browser-open step of `codegraph login` was hardened the same way. Thanks @luoyerr for the report and root-cause analysis. (#1092)
|
||||||
- C++ forward declarations no longer crowd out the real class definition. A `class Foo;` forward declaration — common in large C++ and Unreal Engine codebases, where a heavily used class is forward-declared across dozens of headers — was indexed as its own class node every time it appeared. So exploring that class returned mostly forward-declaration sites, and could even pick one of them as the representative for blast-radius, burying the actual definition and its members and callers. Bodiless forward declarations are now skipped for C and C++, exactly as forward-declared structs and enums already were, so only the real definition is indexed. Languages where a class with no body is a complete definition — such as Kotlin's `class Empty` and Scala — are unaffected. Thanks @luoyxy for the report and root-cause analysis. (#1093)
|
- C++ forward declarations no longer crowd out the real class definition. A `class Foo;` forward declaration — common in large C++ and Unreal Engine codebases, where a heavily used class is forward-declared across dozens of headers — was indexed as its own class node every time it appeared. So exploring that class returned mostly forward-declaration sites, and could even pick one of them as the representative for blast-radius, burying the actual definition and its members and callers. Bodiless forward declarations are now skipped for C and C++, exactly as forward-declared structs and enums already were, so only the real definition is indexed. Languages where a class with no body is a complete definition — such as Kotlin's `class Empty` and Scala — are unaffected. Thanks @luoyxy for the report and root-cause analysis. (#1093)
|
||||||
- C++ methods that return a reference, and user-defined conversion operators, are now indexed under their correct names. An inline getter like `const FGameplayTagContainer& GetActiveTags() const` — everywhere in Unreal Engine headers — was indexed as `& GetActiveTags() const` instead of `GetActiveTags`, and a conversion operator like `operator EALSMovementState() const` kept its trailing `() const` instead of reading `operator EALSMovementState`. In both cases the garbled name meant you couldn't find the symbol by name and its callers weren't linked. Both now read cleanly, matching how pointer-returning and value-returning methods already worked. (#1096)
|
- C++ methods that return a reference, and user-defined conversion operators, are now indexed under their correct names. An inline getter like `const FGameplayTagContainer& GetActiveTags() const` — everywhere in Unreal Engine headers — was indexed as `& GetActiveTags() const` instead of `GetActiveTags`, and a conversion operator like `operator EALSMovementState() const` kept its trailing `() const` instead of reading `operator EALSMovementState`. In both cases the garbled name meant you couldn't find the symbol by name and its callers weren't linked. Both now read cleanly, matching how pointer-returning and value-returning methods already worked. (#1096)
|
||||||
- C++ functions written with an inline-specifier macro before the return type are now indexed correctly. In Unreal Engine, inline helpers are commonly written `FORCEINLINE FString GetEnumerationToString(...)`; the `FORCEINLINE` macro made the parser read the return type as part of the function's name (`FString GetEnumerationToString` instead of `GetEnumerationToString`) and lose the real return type, so the function couldn't be found by name and its callers weren't linked. CodeGraph now recognizes the standard Unreal inline macros (`FORCEINLINE`, `FORCENOINLINE`, `FORCEINLINE_DEBUGGABLE`), so both the name and the return type are captured. (#1100)
|
- C++ functions written with an inline-specifier macro before the return type are now indexed correctly. In Unreal Engine, inline helpers are commonly written `FORCEINLINE FString GetEnumerationToString(...)`; the `FORCEINLINE` macro made the parser read the return type as part of the function's name (`FString GetEnumerationToString` instead of `GetEnumerationToString`) and lose the real return type, so the function couldn't be found by name and its callers weren't linked. CodeGraph now recognizes the standard Unreal inline macros (`FORCEINLINE`, `FORCENOINLINE`, `FORCEINLINE_DEBUGGABLE`), so both the name and the return type are captured. (#1100)
|
||||||
|
|||||||
@@ -71,6 +71,23 @@ function runShim(pkgDir: string, args: string[], env: Record<string, string>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Static source guard (all platforms): every child spawn in the shim must set
|
||||||
|
// windowsHide, or a console (conhost) window flashes when the shim runs as a
|
||||||
|
// background MCP server on Windows (issue #1092). windowsHide is a Windows-only
|
||||||
|
// spawn behavior that can't be observed from these POSIX-only subprocess tests,
|
||||||
|
// so we assert it at the source level instead — and this also catches any new
|
||||||
|
// spawn site added to the shim later.
|
||||||
|
describe('npm-shim windowsHide (#1092)', () => {
|
||||||
|
it('sets windowsHide: true on every spawn in the shim', () => {
|
||||||
|
const src = fs.readFileSync(SHIM_SRC, 'utf8');
|
||||||
|
const spawnLines = src.split('\n').filter((l) => /\.spawn(Sync)?\(/.test(l));
|
||||||
|
expect(spawnLines.length).toBeGreaterThan(0); // guard against a false pass if the calls move
|
||||||
|
for (const line of spawnLines) {
|
||||||
|
expect(line, `spawn without windowsHide: ${line.trim()}`).toContain('windowsHide: true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe.skipIf(isWindows)('npm-shim launcher', () => {
|
describe.skipIf(isWindows)('npm-shim launcher', () => {
|
||||||
it('runs the installed optional-dependency bundle without any download', async () => {
|
it('runs the installed optional-dependency bundle without any download', async () => {
|
||||||
const pkg = makePkg();
|
const pkg = makePkg();
|
||||||
|
|||||||
+2
-2
@@ -45,7 +45,7 @@ async function main() {
|
|||||||
// Happy path: the npm-installed optional dependency. Fall back to a download
|
// Happy path: the npm-installed optional dependency. Fall back to a download
|
||||||
// when the registry didn't deliver it.
|
// when the registry didn't deliver it.
|
||||||
var resolved = resolveInstalledBundle() || (await selfHealBundle());
|
var resolved = resolveInstalledBundle() || (await selfHealBundle());
|
||||||
var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit' });
|
var res = childProcess.spawnSync(resolved.command, resolved.args, { stdio: 'inherit', windowsHide: true });
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
process.stderr.write('codegraph: ' + res.error.message + '\n');
|
process.stderr.write('codegraph: ' + res.error.message + '\n');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -222,7 +222,7 @@ function extract(archive, destDir) {
|
|||||||
var args = isWindows
|
var args = isWindows
|
||||||
? ['-xf', archive, '-C', destDir, '--strip-components=1']
|
? ['-xf', archive, '-C', destDir, '--strip-components=1']
|
||||||
: ['-xzf', archive, '-C', destDir, '--strip-components=1'];
|
: ['-xzf', archive, '-C', destDir, '--strip-components=1'];
|
||||||
var res = childProcess.spawnSync('tar', args, { stdio: 'ignore' });
|
var res = childProcess.spawnSync('tar', args, { stdio: 'ignore', windowsHide: true });
|
||||||
if (res.error) throw new Error('tar unavailable: ' + res.error.message);
|
if (res.error) throw new Error('tar unavailable: ' + res.error.message);
|
||||||
if (res.status !== 0) throw new Error('tar exited ' + res.status);
|
if (res.status !== 0) throw new Error('tar exited ' + res.status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export async function openBrowser(url: string): Promise<void> {
|
|||||||
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
|
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
|
||||||
: ['xdg-open', [url]];
|
: ['xdg-open', [url]];
|
||||||
try {
|
try {
|
||||||
const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true });
|
const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true, windowsHide: true });
|
||||||
child.on('error', () => {});
|
child.on('error', () => {});
|
||||||
child.unref();
|
child.unref();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ export function hasCommand(cmd: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function defaultRun(cmd: string, args: string[], env?: NodeJS.ProcessEnv): number {
|
export function defaultRun(cmd: string, args: string[], env?: NodeJS.ProcessEnv): number {
|
||||||
const r = spawnSync(cmd, args, { stdio: 'inherit', env: env ?? process.env });
|
const r = spawnSync(cmd, args, { stdio: 'inherit', env: env ?? process.env, windowsHide: true });
|
||||||
if (r.error) return -1;
|
if (r.error) return -1;
|
||||||
return r.status ?? -1;
|
return r.status ?? -1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user