Skip to content

fix(serve): bound the whole request head, not just each read - #2684

Merged
steipete merged 3 commits into
steipete:mainfrom
OfficialAbhinavSingh:fix/serve-request-deadline
Aug 6, 2026
Merged

fix(serve): bound the whole request head, not just each read#2684
steipete merged 3 commits into
steipete:mainfrom
OfficialAbhinavSingh:fix/serve-request-deadline

Conversation

@OfficialAbhinavSingh

Copy link
Copy Markdown
Contributor

Closes #2683.

Problem

readRequest bounds each recv with requestReadTimeoutMilliseconds (5s) but nothing bounds the request head as a whole:

while data.count < 16384 {
    guard waitForReadable(fd, timeoutMilliseconds: requestReadTimeoutMilliseconds) else {
        return .failure(.invalidRequest)
    }
    ...
}

A client that keeps trickling bytes just inside that window never trips the timeout, so it holds its connection — and the cooperative-executor thread serving it — for as long as it keeps sending.

This is pre-auth: the Host allowlist and the bearer-token check both run only after the head has been read. And over-cap connections are not queued — connectionGate.tryAcquire() fails and the socket is closed immediately — so legitimate clients get dropped outright rather than waiting their turn.

Worth noting: a client that simply goes silent is already handled correctly by the per-read timeout. The gap is specifically the client that keeps sending.

Fix

Track a monotonic start time, cap each wait at the remaining budget, and fail the request once the overall deadline passes. 10s ceiling, generous for a legitimate head but bounded.

Proof (Linux, swift:6.3.3)

The test boots the real CLILocalHTTPServer on an ephemeral port, opens maximumConnections sockets that each send one header byte per second and never terminate the header block, then retries a well-behaved GET /health against a 25s budget.

Before:

✘ a well-behaved client never got a connection slot within 25s;
  trickling clients held every slot because the request head has no overall deadline
  (failed after 25.776 seconds)

After:

✔ trickling clients cannot hold connection slots indefinitely (passed after 10.260 seconds)

The 10.26s matches the deadline exactly. I reverted the fix and re-ran to confirm the test genuinely fails without it, rather than trusting a green run.

Full Linux suite 356/356, swiftformat --lint clean, swiftlint --strict 0 violations / 1814 files, and Scripts/regenerate-codex-parser-hash.sh check is current.

Scope

Default bind is loopback, so in practice this is a local user wedging the daemon; it becomes network-reachable only with --host 0.0.0.0, which already demands --allow-plain-http and a token. I treated it as robustness hardening rather than a privilege-boundary issue — happy to be corrected on that framing.

Deliberately out of scope

sendResponse is likewise a blocking send loop with no SO_SNDTIMEO, so a client that never drains its receive window can stall a writer. Same class, separate fix; left out to keep this diff small. Happy to follow up if you want it.

`readRequest` applied `requestReadTimeoutMilliseconds` per `recv` with no overall
limit, so a client trickling one byte just inside that window never timed out and
held its connection — and the cooperative-executor thread serving it — for as long
as it kept sending. The Host allowlist and bearer-token check both run after the
head is read, and over-cap connections are closed rather than queued, so a few such
clients denied service to well-behaved ones entirely pre-auth.

Track a monotonic start time and cap each wait at the remaining budget, failing the
request once the overall deadline passes. A client that merely goes silent was
already handled by the per-read timeout; this covers the one that keeps sending.

Verified red→green on Linux: before, a legitimate client never got a slot within a
25s budget; after, it is served at 10.26s, matching the deadline.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fa5390003

ℹ️ 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".

Comment on lines +3 to +6
#if canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard the syscall test from macOS builds

When macOS Swift tests run for this non-doc change, CodexBarLinuxTests is still declared unconditionally in Package.swift, so this new source file is compiled on macOS too. This import block has no Darwin branch, and the code below uses the Linux-only SOCK_STREAM.rawValue form; on the macOS compile path that fails before any tests can run. Please wrap the file in #if os(Linux) like the other Linux syscall tests or add a proper Darwin socket branch.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks — that's a real break, not a flake, and I should have caught it.

TestsLinux is declared unconditionally in Package.swift, so the file compiled on macOS where SOCK_STREAM.rawValue and the Glibc imports don't exist — both shards failed before any test ran. My local pipeline is Linux-in-Docker, so it couldn't see this.
Fixed in 009dbbf: wrapped the file in #if canImport(Glibc) || canImport(Musl), matching AntigravityProcessLauncherLinuxTests and the other syscall suites in that directory rather than inventing a new pattern.
Re-verified after the change — Linux suite still passes (10.26s, matching the 10s deadline), swiftformat --lint clean, swiftlint --strict 0 violations / 1814 files.

`TestsLinux` is declared unconditionally in Package.swift, so this file also
compiles on macOS, where the raw socket calls (`SOCK_STREAM.rawValue`, Glibc-only
imports) do not build and broke both macOS test shards.

Wrap the file in `#if canImport(Glibc) || canImport(Musl)`, matching
AntigravityProcessLauncherLinuxTests and the other syscall suites here.
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 5, 2026
@clawsweeper

clawsweeper Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed August 5, 2026, 9:56 PM ET / August 6, 2026, 01:56 UTC.

ClawSweeper review

What this changes

The PR adds a total deadline for one codexbar serve HTTP request head and a Linux socket regression test for clients that continuously trickle incomplete headers.

Merge readiness

⚠️ Needs maintainer review before merge - 3 items remain

Keep open: the unchanged PR head still starts the total deadline only when its client task receives an executor worker, after the connection slot is acquired. Capture the deadline at acceptance and add a saturated-queue regression test before merge.

Likely related people: steipete — initial local HTTP server author (high confidence).

Priority: P2
Reviewed head: 3e560206f87f06366395b07d05a42b2887e606a8

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) Real behavior proof is strong, but the remaining acceptance-time deadline defect prevents merge readiness.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
Patch quality 🦐 gold shrimp (3/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
Evidence reviewed 5 items Current server lifecycle: Current main acquires the connection slot before creating the client task; the task then enters the request-reading path. A deadline initialized inside that path excludes time spent waiting for a cooperative-executor worker.
Unfixed prior blocker: The supplied prior review raised this exact P2 concern on head 3e56020; the current PR metadata names the same head SHA, so no commit has addressed it.
Feature provenance: Blame attributes the connection gate, accept loop, task creation, and reader lifecycle to the original local-server implementation commit.
Findings 1 actionable finding [P2] Start the deadline before scheduling the client task
Security None None.

How this fits together

CodexBar’s local HTTP server accepts CLI health and control requests, limits concurrent connections, then parses and validates request headers before dispatching the handler. This change bounds pre-auth header reading so incomplete requests release a connection slot.

flowchart LR
    A[Client socket] --> B[Connection slot gate]
    B --> C[Client task]
    C --> D[Request-head reader]
    D --> E{Complete before deadline?}
    E -->|Yes| F[Host and token validation]
    E -->|No| G[Close socket and release slot]
    F --> H[HTTP handler response]
Loading

Before merge

  • Start the deadline before scheduling the client task (P2) - The connection slot is acquired before Task is created, but the new clock starts only after that task gets a cooperative-executor worker. Accepted trickling sockets queued behind blocked readers therefore retain slots without spending their budget, allowing successive full deadline windows; capture an absolute deadline here and pass it into request reading. This is the still-unfixed prior review finding on the unchanged head.
  • Resolve merge risk (P1) - A saturated cooperative executor can leave accepted trickling sockets holding connection-gate slots for successive full deadline windows, so legitimate clients may still be rejected for longer than the advertised ten-second bound.
  • Complete next step (P2) - The remaining blocker is a narrow mechanical deadline-propagation repair with a clear source boundary and regression scenario.

Findings

  • [P2] Start the deadline before scheduling the client task — Sources/CodexBarCLI/CLILocalHTTPServer.swift:401-406
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test delta production +37/-4, tests +198 The focused Linux integration test is substantial coverage for a small server-lifecycle change, but it does not cover the queued-task timing path.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #2683
Summary: This PR is the explicitly linked candidate fix for the request-head deadline bug.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Bound accepted sockets from acceptance time (recommended)
    Capture a monotonic deadline before creating the client task, pass it to request reading, and add a saturation test that verifies queued accepted sockets cannot retain slots beyond that deadline.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Capture the request-head deadline before client-task creation, pass the absolute deadline through the reader, and add a saturated executor/connection-gate regression test proving slots release within one budget.

Technical review

Best possible solution:

Make the request-head deadline run from socket acceptance through header completion, and prove that queued accepted clients release their slots within that single budget.

Do we have a high-confidence way to reproduce the issue?

Yes—current-main source shows slots are acquired before task scheduling, and the supplied real-server Linux transcript establishes the slow-trickle failure mode; this review did not execute tests under the read-only contract.

Is this the best way to solve the issue?

No—the total-read deadline is the right narrow fix, but it must start at socket acceptance rather than when a delayed client task begins reading.

Full review comments:

  • [P2] Start the deadline before scheduling the client task — Sources/CodexBarCLI/CLILocalHTTPServer.swift:401-406
    The connection slot is acquired before Task is created, but the new clock starts only after that task gets a cooperative-executor worker. Accepted trickling sockets queued behind blocked readers therefore retain slots without spending their budget, allowing successive full deadline windows; capture an absolute deadline here and pass it into request reading. This is the still-unfixed prior review finding on the unchanged head.
    Confidence: 0.97

Overall correctness: patch is incorrect
Overall confidence: 0.95

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4cdb349cbc57.

Labels

Label justifications:

  • P2: The remaining bounded-connection failure affects codexbar serve availability but is a limited-scope reliability defect.
  • merge-risk: 🚨 availability: Merging without an acceptance-time deadline can leave connection slots occupied beyond the intended bound under executor saturation.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes a red-to-green terminal transcript from a real ephemeral CLILocalHTTPServer with trickling sockets and a recovered health request.

Evidence

Acceptance criteria:

  • [P1] swift test --filter CLIServeRequestDeadlineLinuxTests.
  • [P1] make test.
  • [P1] make check.

What I checked:

Likely related people:

  • steipete: Current-main blame attributes the connection gate, accept loop, client task, and request reader to this implementation commit. (role: initial server implementation author; confidence: high; commits: 6afa6728f3c4; files: Sources/CodexBarCLI/CLILocalHTTPServer.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Capture the absolute deadline before task creation and pass it to the request reader.
  • Extend the Linux regression to cover queued accepted clients under executor saturation, then rerun the focused test and repository checks.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (4 earlier review cycles)
  • reviewed 2026-08-05T19:55:20.664Z sha 009dbbf :: needs maintainer review before merge. :: none
  • reviewed 2026-08-05T20:20:44.448Z sha 009dbbf :: needs maintainer review before merge. :: none
  • reviewed 2026-08-05T20:26:39.928Z sha 3e56020 :: needs changes before merge. :: [P2] Start the deadline when the socket is accepted
  • reviewed 2026-08-06T00:03:41.885Z sha 3e56020 :: needs changes before merge. :: [P2] Start the deadline when the socket is accepted

The deadline test held executor threads for the full 10s production budget, which
starved unrelated suites: CLIHooksWatchSleepLinuxTests asserts a stop signal is
honored within 2s and instead measured 10.31s on CI. `.serialized` only orders
cases within a suite, so it does not prevent that.

Make the budget injectable on CLILocalHTTPServer, defaulting to the production
value, and have the test pass 1500ms. Full suite drops from ~10.5s to ~2.0s.

Verified the test still fails when the deadline logic is disabled (12.37s
starvation), so the shorter budget did not weaken it, and ran the full suite 8x
with no flake.
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 5, 2026
@steipete
steipete merged commit 9749b99 into steipete:main Aug 6, 2026
9 checks passed
@steipete

steipete commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Landed. Verified before merge: bound math checked (monotonic clock, wraparound-safe elapsed, per-poll wait = min(5s, remaining), 16 KB + 10 s total regardless of trickle rate); Host allowlist and bearer auth confirmed to run only after the head read, so this closes the pre-auth slowloris hold. Status mapping stays within the server’s existing 400/403 vocabulary — right call for a loopback daemon. The Linux trickle test is genuinely adversarial and correctly platform-gated. Local: 89 macOS CLIServe tests pass on the branch; PR CI fully green incl. both Linux builds. Thanks @OfficialAbhinavSingh!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] codexbar serve has no overall request deadline — trickling clients can hold every connection slot pre-auth

2 participants