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:
parent
7f5ca910e3
commit
a0eb68c3c5
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue