mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-18 19:02:18 +02:00
fix(browse): absorb #2414 residuals — EPERM-alive liveness + Windows-dead test tripwires (re-derived)
Re-derive of PR #2414 (SYKhayyat) onto current main. Most of the PR already landed in earlier waves: the tick-derived RESPAWN_GUARD_WINDOW_MS, the spawnTerminalAgent windowsHide flag, the process-liveness regression tests, and the browse/test import.meta.path sweep are all on main. Two pieces remained: 1. isProcessAlive EPERM semantics (error-handling.ts): on the signal-0 path, EPERM means the process EXISTS but we lack rights to signal it — that is ALIVE. Returning false made callers that validate liveness before killing (killAgentByRecord, the terminal-agent watchdog) skip the kill and respawn around a survivor — the self-reinforcing one-leak-per-tick chain from #2414/#2295. Matters for cross-user PID checks. 2. Six test/ files ADDED SINCE the PR reintroduced the exact Windows bug its second commit fixed: `new URL(import.meta.url).pathname` yields `/C:/Users/...` on Windows, so path.resolve prepends the cwd drive and every tripwire ENOENTs instead of asserting anything (egress-receipt, egress-lib, egress-receipt-wiring, gstack-egress-cli, pty-skill-seeding-wiring, skill-census). All six now use import.meta.path — Bun's absolute native path, identical arity. The remaining #2414 piece — replacing the Windows tasklist probe with signal-0 — lands as its own commit (#1952) on top of this shape. Tests: the 6 touched test files 47 pass; process-liveness-windows + error-handling 13 pass. Re-derived from PR #2414 by @SYKhayyat. Fixes the residual of #2295. Co-authored-by: SYKhayyat <shaulyoelkhayyat@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
SYKhayyat
Claude Fable 5
parent
25ccda996d
commit
b3a27173fe
@@ -36,7 +36,15 @@ export function safeKill(pid: number, signal: NodeJS.Signals | number): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if a PID is alive. Pure boolean probe — returns false for ALL errors. */
|
/**
|
||||||
|
* Check if a PID is alive. Pure boolean probe — never throws.
|
||||||
|
*
|
||||||
|
* EPERM means the process EXISTS but we lack rights to signal it. That is
|
||||||
|
* alive — returning false there makes callers that validate liveness before
|
||||||
|
* killing (killAgentByRecord, the terminal-agent watchdog) skip the kill and
|
||||||
|
* respawn around a survivor, leaking one process per watchdog tick (#2414,
|
||||||
|
* #2295).
|
||||||
|
*/
|
||||||
export function isProcessAlive(pid: number): boolean {
|
export function isProcessAlive(pid: number): boolean {
|
||||||
if (IS_WINDOWS) {
|
if (IS_WINDOWS) {
|
||||||
try {
|
try {
|
||||||
@@ -52,7 +60,7 @@ export function isProcessAlive(pid: number): boolean {
|
|||||||
try {
|
try {
|
||||||
process.kill(pid, 0);
|
process.kill(pid, 0);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch (err: any) {
|
||||||
return false;
|
return err?.code === 'EPERM';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import * as os from 'os';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { listReceipts, sha256Hex } from '../lib/egress-receipt';
|
import { listReceipts, sha256Hex } from '../lib/egress-receipt';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
const LIB = path.join(ROOT, 'bin', 'gstack-egress-lib.sh');
|
const LIB = path.join(ROOT, 'bin', 'gstack-egress-lib.sh');
|
||||||
|
|
||||||
const received: string[] = [];
|
const received: string[] = [];
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { describe, test, expect } from 'bun:test';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
|
|
||||||
function read(rel: string): string {
|
function read(rel: string): string {
|
||||||
return fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
return fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
writeReceipt,
|
writeReceipt,
|
||||||
} from '../lib/egress-receipt';
|
} from '../lib/egress-receipt';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
|
|
||||||
let home: string;
|
let home: string;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
writeReceipt,
|
writeReceipt,
|
||||||
} from '../lib/egress-receipt';
|
} from '../lib/egress-receipt';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
const BIN = path.join(ROOT, 'bin', 'gstack-egress');
|
const BIN = path.join(ROOT, 'bin', 'gstack-egress');
|
||||||
|
|
||||||
let home: string;
|
let home: string;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { describe, test, expect } from 'bun:test';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
|
|
||||||
/** A PTY send whose payload starts with a slash command (`/name`, optionally
|
/** A PTY send whose payload starts with a slash command (`/name`, optionally
|
||||||
* followed by `\r`, whitespace, or the closing quote). A second slash right
|
* followed by `\r`, whitespace, or the closing quote). A second slash right
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { frontmatterName, skillCensus } from './helpers/skill-census';
|
import { frontmatterName, skillCensus } from './helpers/skill-census';
|
||||||
|
|
||||||
const ROOT = path.resolve(new URL(import.meta.url).pathname, '..', '..');
|
const ROOT = path.resolve(import.meta.path, '..', '..');
|
||||||
const census = skillCensus(ROOT);
|
const census = skillCensus(ROOT);
|
||||||
|
|
||||||
describe('skillCensus', () => {
|
describe('skillCensus', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user