Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions server/src/lib/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { Disposable } from 'vscode';
import type { Readable } from 'stream';
import type { LogPrefix } from './log';
import { log } from './log';
import { stripPhpstanAgentDetectorEnvVars } from '../../../shared/phpstanSpawnEnv';

export class Process implements AsyncDisposable {
private readonly _children: Set<number> = new Set();
Expand Down Expand Up @@ -178,6 +179,10 @@ export class Process implements AsyncDisposable {
'Spawning PHPStan, command: ',
[binStr, ...args.map((a) => `"${a}"`)].join(' ')
);
const spawnEnv = stripPhpstanAgentDetectorEnvVars({
...process.env,
...options.env,
});
const proc = await (async () => {
if (process.platform === 'win32') {
const codePage = await this._getCodePage();
Expand All @@ -194,6 +199,7 @@ export class Process implements AsyncDisposable {
{
cwd: options.cwd,
encoding: 'utf-8',
env: spawnEnv,
}
);
}
Expand All @@ -203,6 +209,7 @@ export class Process implements AsyncDisposable {
});
return spawn(binStr, args, {
...options,
env: spawnEnv,
stdio: ['pipe', 'pipe', 'overlapped'],
});
})();
Expand Down
9 changes: 8 additions & 1 deletion server/src/start/getVersion.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { stripPhpstanAgentDetectorEnvVars } from '../../../shared/phpstanSpawnEnv';
import { ConfigurationManager } from '../lib/checkConfigManager';
import { SPAWN_ARGS } from '../../../shared/constants';
import type { ClassConfig } from '../lib/types';
Expand All @@ -18,10 +19,15 @@ export async function getVersion(
// Test if we can get the PHPStan version
const cwd = await ConfigurationManager.getCwd(classConfig, true);
const workspaceRoot = (await classConfig.workspaceFolders.get())?.default;
const workspaceRootPath =
workspaceRoot &&
typeof (workspaceRoot as { fsPath?: unknown }).fsPath === 'string'
? (workspaceRoot as { fsPath: string }).fsPath
: undefined;
const binConfigResult = await ConfigurationManager.getBinComand(
classConfig,
cwd ?? undefined,
workspaceRoot?.fsPath
workspaceRootPath
);
if (!binConfigResult.success) {
return {
Expand All @@ -34,6 +40,7 @@ export async function getVersion(
const binArgs = binConfigResult.getBinCommand(['--version']);
const proc = spawn(binArgs[0], binArgs.slice(1), {
...SPAWN_ARGS,
env: stripPhpstanAgentDetectorEnvVars({ ...process.env }),
});

let data = '';
Expand Down
28 changes: 28 additions & 0 deletions shared/phpstanSpawnEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Environment variable names PHPStan uses to detect AI coding agents
* (see PHPStan AgentDetector). When the extension runs inside an agent-capable
* editor, these may be set and skew analysis; they are cleared for child processes.
*/
export const PHPSTAN_AGENT_DETECTOR_ENV_VARS = [
'AI_AGENT',
'CURSOR_TRACE_ID',
'CURSOR_AGENT',
'GEMINI_CLI',
'CODEX_SANDBOX',
'AUGMENT_AGENT',
'OPENCODE_CLIENT',
'OPENCODE',
'CLAUDECODE',
'CLAUDE_CODE',
'REPL_ID',
] as const;

export function stripPhpstanAgentDetectorEnvVars<T extends NodeJS.ProcessEnv>(
env: T
): T {
const out = { ...env };
for (const key of PHPSTAN_AGENT_DETECTOR_ENV_VARS) {
delete out[key];
}
return out;
}