fix(agent-manager): harden PR refresh state

This commit is contained in:
marius-kilocode
2026-08-20 14:22:41 +02:00
parent a9e9570b6f
commit 3f770e66f7
4 changed files with 16 additions and 4 deletions
@@ -249,7 +249,7 @@ export class PRStatusPoller {
const pr = await this.cachedFetchPR(wt.branch, wt.path)
if (!pr || this.stale(generation)) {
if (this.stale(generation)) return
const hash = `${worktreeId}:none`
const hash = `${worktreeId}:${wt.branch}:none`
if (this.lastHash.get(worktreeId) === hash) return
this.lastHash.set(worktreeId, hash)
this.options.onStatus(worktreeId, null)
@@ -4,7 +4,7 @@
* the file, so the lines below the comment are read from disk and attached to
* the thread.
*/
import { readFile, stat } from "node:fs/promises"
import { readFile, realpath, stat } from "node:fs/promises"
import path from "node:path"
import type { PRComment } from "../types"
@@ -32,7 +32,11 @@ function anchor(hunk: string): string | undefined {
}
async function lines(dir: string, file: string): Promise<string[] | undefined> {
const full = path.join(dir, file)
const root = await realpath(dir).catch(() => undefined)
const full = await realpath(path.resolve(dir, file)).catch(() => undefined)
if (!root || !full) return undefined
const rel = path.relative(root, full)
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return undefined
const info = await stat(full).catch(() => undefined)
if (!info?.isFile() || info.size > SIZE) return undefined
const hit = cache.get(full)
@@ -99,6 +99,14 @@ describe("withContext", () => {
expect(items.map((item) => item.after)).toEqual([undefined, undefined, undefined, undefined])
})
it("rejects a comment path outside the worktree", async () => {
const dir = await repo()
await writeFile(path.join(path.dirname(dir), "outside.ts"), SOURCE.join("\n"))
const [item] = await withContext(dir, [thread({ file: "../outside.ts" })])
expect(item!.after).toBeUndefined()
})
it("keeps every thread, in order, whatever the files say", async () => {
const dir = await repo()
const items = await withContext(dir, [thread({ threadId: "a" }), thread({ threadId: "b", outdated: true })])
@@ -102,7 +102,7 @@ export const PRPanel: Component<PRPanelProps> = (props) => {
// expanded card and the scroll position, so the last list for this PR stays.
const comments = createMemo<{ number: number; value: NonNullable<PRStatus["comments"]> } | undefined>((prev) => {
const next = props.pr.comments
if (next?.total) return { number: props.pr.number, value: next }
if (next) return { number: props.pr.number, value: next }
if (prev && prev.number === props.pr.number) return prev
return undefined
})