Skip to content

Instantly share code, notes, and snippets.

@devm33
Created February 15, 2026 14:25
Show Gist options
  • Select an option

  • Save devm33/172e80f662625e445ba4eec4b0adfc07 to your computer and use it in GitHub Desktop.

Select an option

Save devm33/172e80f662625e445ba4eec4b0adfc07 to your computer and use it in GitHub Desktop.
Memory stress test for Copilot CLI alt-screen mode
#!/usr/bin/env node
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
/**
* Memory-leak stress test for --alt-screen mode.
*
* Spawns dist-cli/index.js inside a pseudo-terminal, sends "ls -R /usr"
* repeatedly, and logs RSS at each iteration so you can spot growth.
*
* Usage:
* node script/mem-stress.mjs [cli_path] [iterations] [delay_seconds]
*
* Examples:
* node script/mem-stress.mjs dist-cli/index.js 20
* node script/mem-stress.mjs ~/code/other/dist-cli/index.js 50 5
*/
import { spawn } from "node-pty";
import { execSync } from "child_process";
import path from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import os from "os";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const CLI = path.join(ROOT, "dist-cli", "index.js");
const CLI_ARG = process.argv[2] || CLI;
const CLI_PATH = path.resolve(CLI_ARG);
const ITERATIONS = parseInt(process.argv[3] || "20", 10);
const DELAY_S = parseInt(process.argv[4] || "4", 10);
if (!fs.existsSync(CLI_PATH)) {
console.error(`ERROR: ${CLI_PATH} not found. Run 'npm run build:cli' first.`);
process.exit(1);
}
// Work in a temp dir so the agent doesn't modify the repo
const workdir = fs.mkdtempSync(path.join(os.tmpdir(), "mem-stress-"));
process.chdir(workdir);
function getRssKb(pid) {
try {
const out = execSync(`ps -o rss= -p ${pid}`, { encoding: "utf8" });
return parseInt(out.trim(), 10) || 0;
} catch {
return 0;
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
console.error(`\nMemory stress test: ${ITERATIONS} iterations, ${DELAY_S}s delay`);
console.error(`CLI: ${CLI_PATH}`);
console.error(`Workdir: ${workdir}\n`);
// Header
const header = "elapsed_s\trss_kb\trss_mb\titeration";
console.log(header);
console.error(header);
const pty = spawn("node", [CLI_PATH, "--alt-screen"], {
name: "xterm-256color",
cols: 120,
rows: 40,
cwd: workdir,
env: {
...process.env,
// Prevent the CLI from trying to authenticate
TERM: "xterm-256color",
},
});
const cliPid = pty.pid;
const startTime = Date.now();
// Collect output to detect prompts
let outputBuffer = "";
pty.onData((data) => {
outputBuffer += data;
// Keep buffer bounded
if (outputBuffer.length > 50000) {
outputBuffer = outputBuffer.slice(-25000);
}
});
function logSample(iteration) {
const elapsed = Math.round((Date.now() - startTime) / 1000);
const rss = getRssKb(cliPid);
const rssMb = (rss / 1024).toFixed(1);
const line = `${elapsed}\t${rss}\t${rssMb}\t${iteration}`;
console.log(line);
console.error(line);
return rss;
}
async function run() {
// Wait for the CLI to start
console.error("Waiting for CLI to start...");
await sleep(5000);
logSample("start");
const samples = [];
for (let i = 1; i <= ITERATIONS; i++) {
// Send the command
pty.write("ls -R /usr\r");
// Wait for output to settle
await sleep(DELAY_S * 1000);
const rss = logSample(i);
samples.push({ iteration: i, rss });
}
// Final idle sample
await sleep(3000);
const finalRss = logSample("final");
samples.push({ iteration: "final", rss: finalRss });
// Summary
const startRss = samples[0]?.rss || 0;
const peakRss = Math.max(...samples.map((s) => s.rss));
const growth = finalRss - startRss;
const growthPct = startRss > 0 ? ((growth / startRss) * 100).toFixed(1) : "N/A";
console.error("\n=== Summary ===");
console.error(`Iterations: ${ITERATIONS}`);
console.error(`Start RSS: ${(startRss / 1024).toFixed(1)} MB`);
console.error(`Peak RSS: ${(peakRss / 1024).toFixed(1)} MB`);
console.error(`Final RSS: ${(finalRss / 1024).toFixed(1)} MB`);
console.error(`Growth: ${(growth / 1024).toFixed(1)} MB (${growthPct}%)`);
if (growth > startRss * 0.5) {
console.error("\n⚠️ WARNING: RSS grew by more than 50% — possible memory leak!");
} else {
console.error("\n✅ RSS growth looks reasonable.");
}
// Clean up
pty.write("exit\r");
await sleep(1000);
pty.kill();
fs.rmSync(workdir, { recursive: true, force: true });
}
run().catch((err) => {
console.error("Fatal error:", err);
pty.kill();
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment