Skip to content

fix: hold one video frame before a resize in a max-speed export - #364

Merged
ThomasK33 merged 30 commits into
mainfrom
fix/max-speed-resize-hold-and-docs
Nov 9, 2026
Merged

ThomasK33 merged 30 commits into
mainfrom
fix/max-speed-resize-hold-and-docs

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Why

PR #358 merged at 934760d. Its last Codex round (round 4) left one finding open: "Preserve the pre-resize hold after a timed flush". This PR fixes that finding. An independent verifier then checked the first head of this PR (a9b8083) and found four more problems. This PR fixes them too.

Terms in this text:

  • A flush writes the output that is waiting into the page.
  • The frame hold is the wait after a flush. It is 16 ms in max-speed timing.
  • A video frame is one frame of the WebM file. The recorder makes 25 per second, so one video frame lasts 40 ms.
  • A cycle is one full-screen colour followed by a resize. The capture rate is the share of cycles in which a frame of the video shows the whole screen before the resize.

The finding in plain words. In max-speed timing, the replay waits before a resize so that the screen before the resize can reach the video. When the timestamp check had already flushed that output, the resize found nothing waiting and did not wait. The screen before the resize can be missing from the video.

The verifier found four more problems with a9b8083:

  1. Two changed versions of the new code passed every test: a flag that is never cleared, and a wait placed before the resize's own flush. Both add an extra 16 ms wait.
  2. docs/USAGE.md still said that max-speed makes a frame at most once per 16 ms of recorded time. That ignores resizes. "Each resize adds one frame" was only an upper bound.
  3. The first PR text said "main 115 s, 9 page calls" for the 200x60 log. That is wrong. Main made 2,403 evaluate page calls in 368.5 s. The text also said that no one reproduced a missing frame. The verifier did.
  4. A wait of 16 ms is shorter than one video frame (40 ms). It cannot guarantee that the screen before a resize reaches the video.

What

  1. The original fix stays. lastFlushUnheld remembers a flush that no wait followed. The resize case pays that wait once. A second resize right after the first does not wait again.

  2. The wait before a resize now lasts one video frame in max-speed timing (problem 4, 26 source lines in src/renderer/ghosttyWeb/backend.ts):

    • VIDEO_FPS = 25 and VIDEO_FRAME_MS = 1000 / VIDEO_FPS. The recorder is Playwright's ffmpeg recorder. Its default is 25 frames per second (kDefaultFps in playwright-core 1.64.1, and it starts ffmpeg with -r 25). The browser context now sets recordVideo.fps to VIDEO_FPS, so the number does not depend on that default. The exported video still has r_frame_rate=25/1.
    • preResizeHoldMs is the larger of the frame hold and one video frame in max-speed timing. In recorded and accelerated timing it is the frame hold, as before.
    • The resize case flushes with preResizeHoldMs. The wait after an earlier timed flush uses it too. A resize waits once, not twice.
  3. Tests (test/unit/renderer/ghosttyWebBackend.test.ts, 67 to 75 tests, with the 5 tests that fix: stop a webm replay only when the renderer stalls #356 added). The 3 cases of a9b8083 and the old case holds one frame hold between the last flush and a resize now expect 40 ms. New cases:

    • holds one video frame, not two, when a timed flush is followed by pending output and a resize (problem 1). The log is output at 0 ms, output at 100 ms, resize at 101 ms. The resize runs one wait after the second flush.
    • holds a max-speed frame hold that is longer than a video frame before a resize (a frame hold of 100 ms stays 100 ms).
    • holds the frame hold of %s mode before a resize, for recorded (16 ms) and accelerated (100 ms). Both stay as they were.
    • records the video at 25 frames per second.

    VIDEO_FRAME_MS goes to waitForDelay, which needs a whole number of milliseconds. The constant now has an invariant that stops the module at load with the message VIDEO_FPS must divide 1000 so that a video frame is a whole number of ms. Every test file that imports the backend runs that check. With VIDEO_FPS = 30 the unit test file fails at load with that message and runs no test. With the check removed and VIDEO_FPS = 30, 7 tests fail with a message about a delay, and the fps test fails too.

  4. Docs and comments (problem 2). I re-read every sentence that this PR changes against the code and the numbers below:

    • docs/USAGE.md: "It makes a frame at most once per 16 ms" now says that recorded time ends a frame at most once per 16 ms. A resize that finds output waiting ends one more frame, outside the 2,400 limit. A resize adds at most one frame and none when no output is waiting. 100,000 outputs that each have a resize 1 ms after them make 100,000 frames. A new sentence names the wait of one video frame before a resize.
    • src/renderer/ghosttyWeb/README.md: the same resize sentence. "An event that changes no pixel does not end it" became a sentence that names its mode. In max-speed mode, an input, marker, run-complete, signal or exit event ends a flush only through the time rule. In recorded and accelerated mode, such an event ends a flush directly (flushBeforeNonVisualEvent). A resize that finds output waiting and the end of the replay end a flush in every mode. The other sentences of that paragraph say which mode they are for. The sentence about waits now lists the three waits of max-speed: after the last flush, after a resize, and one video frame before a resize.
    • src/export/README.md: the same wait list.
    • backend.ts comments: the resize comment on the budget, and the timing comments. The comment on MAX_SPEED_FRAME_BUDGET listed numbers from the first PR text. It now lists measured numbers: 18 to 20 ms per flush at CPU pressure 4 to 22 % (PR perf: write each ghostty-web replay flush in one frame #358), 27 to 54 ms per flush at pressure 38 to 70 % (14 runs of this PR), and 287 s to 405 s for the 2,400-flush log at 200x60. The sentence "this many flushes fill 96 s of video" was wrong because the video is as long as the replay. It now says "of 40 ms or more make a video of 96 s or more". The sentence that said a WebM export allows its replay 5 minutes is gone, because fix: stop a webm replay only when the renderer stalls #356 removed that limit. The comment now says that the export has no limit on the length of its replay and fails only when the renderer makes no progress for 5 minutes (REPLAY_STALL_WINDOW_MS).

Measurements

The screen before a resize reaches the video slightly more often with one video frame than with 16 ms (problem 4). The gain over a 16 ms wait is small. In one verifier run the head missed 3 cycles and the 16 ms wait missed 2.

Method. I made 3 offline sessions. Each has 8 cycles. A cycle fills the screen with one palette colour (SGR 48;5;N), then resizes the terminal to a smaller size (80x24 down to 16x8). The logs differ in the gap before the resize: close (resize 5 ms after the output), input (an input at 100 ms, a resize at 101 ms) and resize (resize 100 ms after the output). I ran record export --format webm --timing max-speed with node --import tsx src/cli/main.ts in two trees of git archive, with the same node_modules. The two trees ran in alternating order, 30 rounds of 3 logs each. I decoded pixel row 6 of every video frame (ffmpeg -vf format=rgb24,crop=760:1:0:6 -fps_mode passthrough -f rawvideo) and counted the frames in which the row has the colour of the cycle over the full width of that screen. A cycle counts as seen when one frame shows it. The host was busy (CPU pressure avg10 41 to 88 %, median 64 %).

Run Code Cycles Screen before the resize in the video
Verifier, run A (one launcher for both trees) main 934760d, no wait 288 279 (96.9 %)
Verifier, pooled (one launcher for both trees) a9b8083, 16 ms wait 768 758 (98.7 %, 10 missed)
Verifier, pooled (same runs) this head, one video frame (40 ms) wait 768 764 (99.5 %, 4 missed)
Mine, 30 rounds a9b8083, 16 ms wait 720 700 (97.2 %, 20 missed)
Mine, same rounds one video frame (40 ms) wait 720 719 (99.9 %, 1 missed)

The verifier's pooled difference is 10 against 4 missed cycles (p = 0.18, not significant). My difference is 20 against 1 (p = 1.9e-5). So the gain over a 16 ms wait is 0.8 to 2.6 points. It depends on the host and the run. The capture rate depends on host load: a busier host leaves more time between frames. By the host CPU pressure (avg10) at the start of each export, main without any wait missed the pre-resize screen in 28 % of cycles below 45, 25 % at 45 to 55, 16 % at 55 to 65 and 2 to 3 % above 65. The 16 ms wait (a9b8083) missed 6 %, 2 %, 1 % and 0 % in the same bands. So main's 96.9 % in run A holds only at pressure near 70, and the earlier 76 % (188 of 248) came from lower load. The head with one video frame was not measured below pressure 45, because the host stayed busier than that.

In my 30 rounds, by log: close 230 of 240 against 239 of 240, input 235 against 240, resize 235 against 240. 18 of 90 exports of a9b8083 lost at least one screen. 1 of 90 exports with the longer wait did. The Fisher exact test is two-sided. The one missed cycle with the longer wait was in the close log at pressure 68 %.

I repeated the run with the code of 426998c (it has the explicit fps): 10 rounds of 3 logs, 240 cycles against a9b8083. It showed all 240 (100 %). a9b8083 showed 232 (96.7 %).

The longer wait adds 24 ms of waiting for each resize that follows output. Real replays cost more than that sum (see the real-browser pairs below). Logs without a resize are not touched.

  • No resize. The wait code only runs at a resize. On a fake clock (the real replay loop with stubbed page calls) a log without a resize has the same number of flushes and the same time in main, in a9b8083 and in this head. That covers the 2,400-flush limit:

    Log (no resize) Flushes Time on the fake clock
    400 outputs, 20 ms apart 400 516 ms
    6,000 outputs, 40 ms apart 2,000 516 ms
    20,000 outputs, 20 ms apart 2,223 532 ms
    100,000 outputs, 1 ms apart 2,381 532 ms
    50,000 outputs, 100 ms apart 2,381 532 ms
    2,400 outputs, 42 ms apart 2,400 516 ms

    Real runs agree: a log of 400 outputs 40 ms apart made 400 flushes and 405 page calls in all 14 runs of both trees.

  • Resizes (fake clock, flushes the same in all three):

    Log Resizes main a9b8083 this head
    6,000 outputs 40 ms apart, a resize 1 ms after every 10th 600 19.7 s 19.7 s 34.1 s
    600 outputs 250 ms apart, a resize 100 ms after each 600 10.1 s 19.7 s 34.1 s
    100,000 outputs 1 ms apart, a resize 1 ms after each 100,000 3,200.5 s 3,200.5 s 5,600.5 s

    The 100,000-flush row is why the USAGE text now says that resizes are outside the 2,400 limit. The a9b80836 column differs from main only when the timestamp check flushed first (row 2).

  • Real browser, resize-heavy log, 7 alternating pairs of mine, replay time only (no video), medians:

    Log a9b8083 this head Pair ratio, median (range)
    400 outputs 40 ms apart, a resize 1 ms after every second output (200 resizes) 33.8 s 40.2 s 1.20 (1.11 to 1.44)
    200 outputs 250 ms apart, a resize 100 ms after each (200 resizes) 40.6 s 41.9 s 1.00 (0.44 to 1.55)
    400 outputs 40 ms apart, no resize (control, same code path) 17.0 s 15.7 s 1.08 (0.76 to 1.15)

    The control shows the noise floor of this busy host: about ±20 %. My second row (1.00, range 0.44 to 1.55) is too noisy to show the cost. Page calls were the same in every pair (805, 605 and 405).

    The verifier measured the 200-resize log in 5 real-browser pairs and found this head slower in all 5. The median ratio was 1.42 (1.12 to 1.54), and the difference was +2.9 s to +11.2 s. The extra wait predicts 200 x 24 ms = 4.8 s. So the cost is 15 to 56 ms per resize, and the verifier's pairs are the better measurement of it.

What stays open:

  • Cycles are still missing with the longer wait: 1 in 720 in my run and 4 in 768 in the verifier's. A wait of one video frame lowers the chance of a miss by a small amount, and a frame can still drop in the recorder.
  • This PR only changes the screen before a resize. The screen after a resize still gets the 16 ms wait, and it is followed by the next flush.
  • A recording with many resizes takes 15 to 56 ms longer for each of them (24 ms of that is the wait). The export has no total time limit since fix: stop a webm replay only when the renderer stalls #356. It fails only when the renderer makes no progress for 5 minutes, and the extra wait of 40 ms is a stretch inside one report interval, so it cannot cause a stall by itself.
  • The 200x60 log of 2,400 repaints is not affected by this PR. The verifier measured 368.5 s on main (2,403 evaluate calls, CPU pressure 27 %) and 405.4 s on a9b8083 (45 %). I did not repeat it. The difference is load, because the code path is the same.

Mutation checks of the new code, on the merge result. Each run is the two unit files ghosttyWebBackend and webm (122 tests). The control with an identical text failed 3 tests of #356 by their 5 s test timeout at CPU pressure 87 %. Those 3 tests (exports a dense 'accelerated' recording whose replay runs far past its plan, the same for 'max-speed', and exports a max-speed recording whose output goes to the renderer in one flush) fail the same way on main (47a8157): 2 of 6 runs on main and 2 of 6 on the merge result failed at pressure 86 to 88 %, and all passed on reruns. I leave them out of the red counts below:

Change to the code Red tests
flag made sticky (||=) holds one video frame, not two, ... (and only it)
wait placed before the resize's own flush holds one video frame, not two, ... (and only it)
never wait after an unheld flush 3
never set the flag 3
never clear the flag 1
wait is the frame hold only (no video frame) 5
wait is one video frame only (drops the larger of the two) holds a max-speed frame hold that is longer than a video frame ...
one video frame in every mode holds the frame hold of recorded mode before a resize
the resize flush keeps the plain frame hold 2
VIDEO_FPS = 30 the module fails at load with the VIDEO_FPS must divide 1000 message and no test runs
no fps in recordVideo records the video at 25 frames per second
(#356 watchdog) no report after the resize call reports progress between the resize call and the note that follow a resize event and exports a recording that resizes often while no page call outlasts the window
(#356 watchdog) report before the resize call, not after it the same 2 tests
(#356 watchdog) no report after the last event 8 tests, among them reports progress after each bridge batch of a timed replay flush and the names event ... hangs tests

The first two rows also go red on a9b8083 with the same new test (with 16 ms in place of 40 ms), and they were green before it.

Merge with main

The branch merges origin/main at 47a8157 (a normal merge, no rebase, no force). Main now has #356 (the stall watchdog) and #363. The merge conflicted in 3 files, in comments and docs only:

  • src/renderer/ghosttyWeb/backend.ts: the comment on MAX_SPEED_FRAME_BUDGET. I kept this PR's measured numbers and rewrote the sentence about a replay limit of 5 minutes. After fix: stop a webm replay only when the renderer stalls #356 the export has no total limit and fails only when the renderer makes no progress for 5 minutes (REPLAY_STALL_WINDOW_MS).
  • src/export/README.md: I kept this PR's timing-preset line and main's stall-window line. The old REPLAY_TIMEOUT_MS line is gone.
  • src/renderer/ghosttyWeb/README.md: I kept main's onProgress line and this PR's replayWithTiming paragraph. Main says that replayWithTiming reports in four places. This PR adds no report, so that count stays true.

Both changes survive in the merged code. A resize event waits once (preResizeHoldMs), then calls the resize, then reports progress for that event (seq of the resize, scheduledWaitMs 0), then notes the painted graphemes. waitForDelay is now a chain of timers and still takes whole milliseconds, and VIDEO_FRAME_MS keeps its assertion. No REPLAY_TIMEOUT_MS or "5 minutes in total" text is left in the tree.

git diff origin/main HEAD shows only this PR: 5 files, 250 added lines and 20 removed lines. The branch history also lists the commits of the old #358 branch, because main has the squash of #358. The diff against main is not affected.

Validation

All commands ran on the merge result bd31626 with Node 26.8.1. The integration and e2e files ran one at a time with --maxWorkers=1 --retry=0.

Command Result
npm run format:check pass
npm run lint 0 warnings, 0 errors
npm run typecheck pass
npx vitest run test/unit 149 files, 2,401 tests pass (third run, see below)
bash .agents/skills/verify-session-lifecycle/scripts/check.sh --drift-only covered code matches the sync record
test/integration/renderer-backend.test.ts 33 tests pass
test/integration/record-export.test.ts 17 tests pass
test/integration/ghostty-web-crash.test.ts 4 tests pass
test/integration/empty-session-capture.test.ts (it exports WebM too) 11 tests pass
test/e2e/export-fixtures.test.ts 5 tests pass

Tests that passed only on rerun. The host was very busy during the first pass (CPU pressure avg10 78 to 88 %). Tests failed by timeout or by a CLI that exited with null. No failing assertion pointed at this PR. The same tests passed on the rerun at pressure 25 to 50 %.

  • Unit, first run: doctor (returns unique passing checks across environment and renderer groups), lifecycle (times out without recording destroyed while the group survives its kill), artifactManifestLock (lets one writer at a time in when many find a dead owner's lock), lineDiff (handles a huge one-sided middle without allocating the DP table) and 2 tests of builtins (registers agent-tty-smoke ... and the one after it). Unit, second run: gc (removes the session folder when a manifest write lands during its rm). The third run passed all 2,401 tests.
  • renderer-backend: records a glyph that a resize brings back from the scrollback. It got missingGlyphsTruncated: true, so the glyph probe ran out of its time under load.
  • record-export: 6 tests (exports deterministic asciicast artifacts for running sessions, creates missing parent folders of --out, fails with EXPORT_ERROR when the --out parent folder cannot be searched, keeps one manifest entry per file when it exports the same seq again, lists a glyph that a webm frame drew as a box ..., names the size limit when the event log is too large to read).
  • ghostty-web-crash: all 4 tests. empty-session-capture: 9 of 11 tests. export-fixtures: captures alt-screen content live ... and supports post-mortem snapshot, screenshot, and asciicast export after a crash.
  • Three tests of fix: stop a webm replay only when the renderer stalls #356 in test/unit/export/webm.test.ts (exports a dense 'accelerated' recording whose replay runs far past its plan, the same for 'max-speed', and exports a max-speed recording whose output goes to the renderer in one flush) hit their 5 s test timeout at pressure 86 to 88 %. They do the same on main: 2 of 6 runs on main and 2 of 6 runs on the merge result failed, and all passed in the other runs.

I also ran the real CLI (record export --format webm --timing max-speed) on 3 crafted sessions about 240 times for the measurements above.


Generated with xum • Model: anthropic:claude-sonnet-5-5 • Thinking: xhigh

The harness awaited terminal.write(data, callback) for every output
chunk. ghostty-web runs that callback one animation frame later, so a
replay cost about 17-24 ms per event even though Node already sent up
to 1000 chunks per page call.

Add a harness writeBatch(chunks, waitForFrame) that writes all chunks
at once and waits for one frame only when asked. The backend asks for
the frame on the last page call of a flush. Screenshots and snapshots
are byte-identical to before. In a WebM replay, the chunks of one flush
now share one video frame.
… flush once

Max-speed flushed once per run of output events, and a flush is one frame
since the batched write. A WebM of an output-only recording then showed only
its final state. Max-speed now flushes every frame hold (16 ms) of recorded
time and holds the frame only after the last flush of a run, so it is never
slower than one frame per output event.

A flush of more than 1000 chunks takes several page calls. ghostty-web paints
on every animation frame, so a state between two calls reached the video. The
page now holds the chunks of a flush until the call that ends it and writes
them in one task. Tests pin flushes of exactly 1000 and 2000 chunks and read
the screencast frames.
…peed frames, drop held chunks after a failed flush
…hold-and-docs

# Conflicts:
#	docs/USAGE.md
#	src/renderer/ghosttyWeb/README.md
#	src/renderer/ghosttyWeb/backend.ts
#	test/unit/renderer/ghosttyWebBackend.test.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T18:37:30.522028Z bd31626 Manual request
🔒 Security Review ✅ Completed 2026-11-09T18:38:25.649572Z bd31626 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: a9b80836dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: a9b80836dc

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

The 16 ms frame hold before a resize is shorter than one video frame
(40 ms at 25 frames per second), so the recorder missed the screen before
a resize in 20 of 720 resizes in an interleaved test (719 of 720 seen with
a hold of one video frame). A max-speed replay now holds the larger of the
frame hold and one video frame before a resize. Recorded and accelerated
replays keep their frame hold. The context sets the 25 frames per second
that the hold is built on.

A new test pins one hold when a timed flush, pending output and a resize
follow each other. The tests for the sticky flag and for a hold before the
resize's own flush turn red.
The USAGE sentence that said max-speed makes a frame at most once per
16 ms ignored resizes, and a resize adds at most one frame, not one each.
The renderer and export READMEs now describe the one-video-frame hold
before a resize. The budget comment lists the measured flush costs.
@ThomasK33 ThomasK33 changed the title fix: keep the frame hold before a resize after a timed max-speed flush fix: hold one video frame before a resize in a max-speed export Nov 9, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 426998cc30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 426998cc30

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

The max-speed wait before a resize passes VIDEO_FRAME_MS to waitForDelay,
which needs a whole number. A VIDEO_FPS that does not divide 1000 now
fails when the module loads, with a message that names the cause.
An event that changes no pixel ends a flush only through the time rule in
max-speed mode. In recorded and accelerated mode it ends a flush directly.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: c8345e640e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: c8345e640e

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

# Conflicts:
#	src/export/README.md
#	src/renderer/ghosttyWeb/README.md
#	src/renderer/ghosttyWeb/backend.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: bd316261c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: bd316261c4

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 776ee5c Nov 9, 2026
27 checks passed
@ThomasK33
ThomasK33 deleted the fix/max-speed-resize-hold-and-docs branch November 9, 2026 19:09
@github-actions github-actions Bot mentioned this pull request Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant