fix: address 5 bugbot findings — symlink type, timeout cleanup, file validation, repo format

- spawn.ts: pass `type` param to symlinkSync based on lstat (dir vs file)
- spawn.ts: document TOCTOU gap in getNextSessionNumber (tmux rejects dupes)
- dashboard.ts: store browser timeout and clear it on child exit
- send.ts: wrap file read in try-catch with user-friendly error
- review-check.ts: validate owner/name split before GraphQL query

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-14 16:01:54 +05:30
parent 7f5ca910e3
commit a0eb68c3c5
4 changed files with 25 additions and 4 deletions

View File

@ -78,8 +78,10 @@ export function registerDashboard(program: Command): void {
process.exit(1);
});
let browserTimer: ReturnType<typeof setTimeout> | undefined;
if (opts.open !== false) {
setTimeout(() => {
browserTimer = setTimeout(() => {
const browser = spawn("open", [`http://localhost:${port}`], {
stdio: "ignore",
});
@ -90,6 +92,7 @@ export function registerDashboard(program: Command): void {
}
child.on("exit", (code) => {
if (browserTimer) clearTimeout(browserTimer);
process.exit(code ?? 0);
});
});

View File

@ -18,6 +18,9 @@ async function checkPRReviews(
prNumber: string,
): Promise<{ pendingComments: number; reviewDecision: string | null }> {
const [owner, name] = repo.split("/");
if (!owner || !name) {
return { pendingComments: 0, reviewDecision: null };
}
// Use GraphQL with variable passing (-F) to avoid injection via repo names
const query =

View File

@ -98,7 +98,13 @@ export function registerSend(program: Command): void {
// Send the message
if (opts.file) {
const content = readFileSync(opts.file, "utf-8");
let content: string;
try {
content = readFileSync(opts.file, "utf-8");
} catch (err) {
console.error(chalk.red(`Cannot read file: ${opts.file} (${err})`));
process.exit(1);
}
const tmpFile = join(tmpdir(), `ao-send-${Date.now()}.txt`);
writeFileSync(tmpFile, content);
try {

View File

@ -1,4 +1,4 @@
import { existsSync, mkdirSync, symlinkSync, unlinkSync } from "node:fs";
import { existsSync, lstatSync, mkdirSync, symlinkSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import chalk from "chalk";
import ora from "ora";
@ -10,6 +10,14 @@ import { banner } from "../lib/format.js";
import { getAgent } from "../lib/plugins.js";
import { escapeRegex } from "../lib/session-utils.js";
/**
* Find the next available session number for a prefix.
*
* There is an inherent TOCTOU gap between reading the session list and creating
* the tmux session. If two spawns race, tmux new-session will fail with a
* duplicate name error, which spawnSession already handles by throwing to the
* caller (batch-spawn catches per-item failures and continues).
*/
async function getNextSessionNumber(prefix: string): Promise<number> {
const sessions = await getTmuxSessions();
let max = 0;
@ -99,7 +107,8 @@ async function spawnSession(
const dest = join(worktreePath, file);
if (existsSync(src) && !existsSync(dest)) {
try {
symlinkSync(src, dest);
const type = lstatSync(src).isDirectory() ? "dir" : "file";
symlinkSync(src, dest, type);
} catch {
// ignore
}