| … | … | @@ -0,0 +1,328 @@ | |
| 88 | + | ||
| 89 | + | /** The old and new path of one file's patch, from its `---`/`+++` lines or its header. */ | |
| 90 | + | export function patchPaths(filePatch: string): { oldPath: string; newPath: string } { | |
| 91 | + | const header = /^diff --git a\/(.+?) b\/(.+)$/m.exec(filePatch); | |
🟡 Medium pullRequest/PullRequestFixtures.ts:91
For a filename containing a double quote, patchPaths returns { oldPath: "", newPath: "" } because Git quotes unusual paths in diff --git headers and the regex only matches unquoted paths. Replay then supplies empty paths to getDiffFilePaths, so expanding context for that file fails; parse Git’s quoted path format or retrieve paths using a NUL-delimited listing.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/pullRequest/PullRequestFixtures.ts around line 91: |
| 2 | |
| 3 | For a filename containing a double quote, `patchPaths` returns `{ oldPath: "", newPath: "" }` because Git quotes unusual paths in `diff --git` headers and the regex only matches unquoted paths. Replay then supplies empty paths to `getDiffFilePaths`, so expanding context for that file fails; parse Git’s quoted path format or retrieve paths using a NUL-delimited listing. |
| … | … | @@ -0,0 +1,143 @@ | |
| 80 | + | const change = recordRevisions(); | |
| 81 | + | const home = NodePath.join(repo, ".t3/stress/record-home"); | |
| 82 | + | const workspace = prepareHome(home, `https://github.com/${repository}.git`); | |
| 83 | + | const server = await startServer({ | |
🟠 High stress/record.ts:83
On a fresh checkout without apps/web/dist, this starts the server without a static directory, so the UI returns 503 and the recording cannot capture reads. stress:record-pr never builds the web client before startServer; build it first or check and report the missing prerequisite before recording.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/stress/record.ts around line 83: |
| 2 | |
| 3 | On a fresh checkout without `apps/web/dist`, this starts the server without a static directory, so the UI returns 503 and the recording cannot capture reads. `stress:record-pr` never builds the web client before `startServer`; build it first or check and report the missing prerequisite before recording. |
| … | … | @@ -0,0 +1,328 @@ | |
| 199 | + | const [repository, number, commit] = key.split("\u0000") as [string, string, string]; | |
| 200 | + | if (commit) { | |
| 201 | + | return splitPatch( | |
| 202 | + | yield* git("getDiff", repository, ["show", "--no-color", "--format=", "-M", commit]), | |
🟠 High pullRequest/PullRequestFixtures.ts:202
A replay diff request with commit: "--output=/absolute/path" makes git show create or truncate that path, so a read can write outside the fixture directory. This call passes commit as an unvalidated Git argument, unlike the normal GitHub implementation; reject non-SHA commits before invoking Git and ensure option parsing is terminated.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/pullRequest/PullRequestFixtures.ts around line 202: |
| 2 | |
| 3 | A replay diff request with `commit: "--output=/absolute/path"` makes `git show` create or truncate that path, so a read can write outside the fixture directory. This call passes `commit` as an unvalidated Git argument, unlike the normal GitHub implementation; reject non-SHA commits before invoking Git and ensure option parsing is terminated. |
| … | … | @@ -0,0 +1,328 @@ | |
| 309 | + | setFilesViewed: () => Effect.void, | |
| 310 | + | }; | |
| 311 | + | for (const method of RECORDED_READS) { | |
| 312 | + | if (method === "getViewer" || provider[method] === undefined) continue; | |
🟡 Medium pullRequest/PullRequestFixtures.ts:312
When a recording lacks getFilesViewed, replay returns a not-found error instead of the empty viewed-file result, causing GitHub's host-backed pullRequestViewedFiles.filesViewed path to fail. The final loop overwrites the fallback at lines 294–297; exclude getFilesViewed from that loop.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/pullRequest/PullRequestFixtures.ts around line 312: |
| 2 | |
| 3 | When a recording lacks `getFilesViewed`, replay returns a `not-found` error instead of the empty viewed-file result, causing GitHub's host-backed `pullRequestViewedFiles.filesViewed` path to fail. The final loop overwrites the fallback at lines 294–297; exclude `getFilesViewed` from that loop. |
| … | … | @@ -0,0 +1,328 @@ | |
| 204 | + | } | |
| 205 | + | const { baseSha, headSha } = yield* revisions(repository, Number(number)); | |
| 206 | + | return splitPatch( | |
| 207 | + | yield* git("getDiff", repository, ["diff", "--no-color", "-M", baseSha, headSha]), | |
🟠 High pullRequest/PullRequestFixtures.ts:207
When the base branch advances after the PR branches off, replay includes base-only changes in the PR diff, so its file list and stats show unrelated changes or apparent reversions. This compares the recorded base tip directly to headSha; record the merge base and use it for the diff, stats, and old-file contents.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/pullRequest/PullRequestFixtures.ts around line 207: |
| 2 | |
| 3 | When the base branch advances after the PR branches off, replay includes base-only changes in the PR diff, so its file list and stats show unrelated changes or apparent reversions. This compares the recorded base tip directly to `headSha`; record the merge base and use it for the diff, stats, and old-file contents. |
| … | … | @@ -0,0 +1,354 @@ | |
| 238 | + | const threads = threadIds.map((threadId) => t3(home, "thread", "show", threadId, "--json")); | |
| 239 | + | const seconds = (key: string) => Math.round(((after[key] ?? 0) - (before[key] ?? 0)) * 1000); | |
| 240 | + | const summary = { | |
| 241 | + | runId, | |
🟡 Medium stress/run.ts:241
PR runs omit the selected pull request identifier, so report() labels them as threads-only and summary.json cannot distinguish their repository or PR number. The summary object never sets the pullRequest field that report() reads; include the selected repository#number, or null when no PR was selected.
| 1 | runId, |
| 2 | pullRequest: pullRequest ? `${pullRequest.repository}#${pullRequest.number}` : null, |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/stress/run.ts around line 241: |
| 2 | |
| 3 | PR runs omit the selected pull request identifier, so `report()` labels them as threads-only and `summary.json` cannot distinguish their repository or PR number. The summary object never sets the `pullRequest` field that `report()` reads; include the selected `repository#number`, or `null` when no PR was selected. |
| … | … | @@ -0,0 +1,354 @@ | |
| 75 | + | */ | |
| 76 | + | async function act(page: Page, step: number) { | |
| 77 | + | if (!pullRequest || step % 2 === 0) { | |
| 78 | + | await openThread(page, `Stress ${(step % THREADS) + 1}`); | |
🟠 High stress/run.ts:76
With --pr --threads 4, the approval visits reach only Stress 1 and Stress 3; Stress 2 and Stress 4 remain blocked waiting for approval, understating concurrent load. Because this branch runs only on even step values, the thread selection skips every other thread; use a separate visit counter for these visits.
| 1 | +let threadVisit = 0; |
| 2 | + |
| 3 | async function act(page: Page, step: number) { |
| 4 | if (!pullRequest || step % 2 === 0) { |
| 5 | - await openThread(page, `Stress ${(step % THREADS) + 1}`); |
| 6 | + await openThread(page, `Stress ${(threadVisit++ % THREADS) + 1}`); |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/stress/run.ts around lines 76-78: |
| 2 | |
| 3 | With `--pr --threads 4`, the approval visits reach only `Stress 1` and `Stress 3`; `Stress 2` and `Stress 4` remain blocked waiting for approval, understating concurrent load. Because this branch runs only on even `step` values, the thread selection skips every other thread; use a separate visit counter for these visits. |
| … | … | @@ -0,0 +1,354 @@ | |
| 262 | + | memory: { | |
| 263 | + | heapMbStart: samples[0]?.heapMb ?? 0, | |
| 264 | + | heapMbEnd: samples.at(-1)?.heapMb ?? 0, | |
| 265 | + | heapMbMax: Math.max(0, ...samples.map((sample) => sample.heapMb)), | |
🟠 High stress/run.ts:263
The reported heap growth excludes allocations made during the first user action, and heapMbMax can miss a higher baseline heap value. Both values are calculated from samples, whose first entry is recorded only after act() completes; use the pre-action before.JSHeapUsedSize baseline for the start and peak.
| 1 | heapMbStart: Math.round((before.JSHeapUsedSize ?? 0) / 2 ** 20), |
| 2 | heapMbEnd: samples.at(-1)?.heapMb ?? 0, |
| 3 | heapMbMax: Math.max( |
| 4 | Math.round((before.JSHeapUsedSize ?? 0) / 2 ** 20), |
| 5 | ...samples.map((sample) => sample.heapMb), |
| 6 | ), |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/web/stress/run.ts around lines 263-265: |
| 2 | |
| 3 | The reported heap growth excludes allocations made during the first user action, and `heapMbMax` can miss a higher baseline heap value. Both values are calculated from `samples`, whose first entry is recorded only after `act()` completes; use the pre-action `before.JSHeapUsedSize` baseline for the start and peak. |