diff --git a/.claude/skills/address-review/SKILL.md b/.claude/skills/address-review/SKILL.md index c58c407..699773d 100644 --- a/.claude/skills/address-review/SKILL.md +++ b/.claude/skills/address-review/SKILL.md @@ -23,11 +23,16 @@ That variable is the whole of the addressing. It names **this tab's** review, so there is no repository to pass and no way to address comments meant for another worktree. -Give every call a deadline — `--max-time 30`. The server runs `git` for some of -these, and a repository being built in another pane can make that slow; a curl -with no deadline turns that into a tool timeout that says nothing about what -went wrong. If one does trip, say so and try once more rather than treating it -as a missing review: the answer was late, not absent. +Give every call a deadline — `--max-time 30` — so a hung request comes back as +a curl error rather than a tool timeout that says nothing about what went wrong. +Every call should answer in well under a second; one that hits the deadline +means something is wrong, not that the server is busy. + +Send every POST with a body, even an empty one (`-d ''`). Without one curl sends +no `Content-Length`, and older playpen builds wait for a body that never comes: +the change is applied, but the response never arrives. If a write does time +out, re-read the comments before retrying: it has probably already landed, and +posting it again leaves a duplicate. If it is empty — you are running outside playpen, or in a shell started before the server came up — discover it instead: @@ -102,7 +107,7 @@ For every pending comment, in order: 4. **Resolve the thread** once it's fully handled (skip if you asked a question): ```bash - curl -s --max-time 30 -X POST "$BASE/api/comments//resolve" + curl -s --max-time 30 -X POST "$BASE/api/comments//resolve" -d '' ``` Replies and resolutions appear in the review pane immediately over its live diff --git a/.claude/skills/leave-review/SKILL.md b/.claude/skills/leave-review/SKILL.md index dd6572a..c6f54d6 100644 --- a/.claude/skills/leave-review/SKILL.md +++ b/.claude/skills/leave-review/SKILL.md @@ -28,11 +28,16 @@ curl -s --max-time 30 "$BASE/api/repo" tab's** review, so there is nothing to choose and no way to leave your review on someone else's branch. -Give every call a deadline — `--max-time 30`. The server runs `git` for some of -these, and a repository being built in another pane can make that slow; a curl -with no deadline turns that into a tool timeout that says nothing about what -went wrong. If one does trip, say so and try once more rather than treating it -as a missing review: the answer was late, not absent. +Give every call a deadline — `--max-time 30` — so a hung request comes back as +a curl error rather than a tool timeout that says nothing about what went wrong. +Every call should answer in well under a second; one that hits the deadline +means something is wrong, not that the server is busy. + +Send every POST with a body, even an empty one (`-d ''`). Without one curl sends +no `Content-Length`, and older playpen builds wait for a body that never comes: +the change is applied, but the response never arrives. If a write does time +out, re-read the comments before retrying: it has probably already landed, and +posting it again leaves a duplicate. - `{"open":true,…}` with a `context` object — good, go to step 2. - `{"open":false}` or a 409 — this tab has no review pane. Tell the user to open diff --git a/src/review/Server.zig b/src/review/Server.zig index 40c2c52..77203b0 100644 --- a/src/review/Server.zig +++ b/src/review/Server.zig @@ -459,6 +459,17 @@ fn serve(self: *Server, conn: std.Io.net.Stream) void { while (self.running.load(.acquire)) { var request = http.receiveHead() catch return; + // A request with neither a length nor chunking has an empty body (RFC + // 9112 §6.3), but std.http reads one until the connection closes, and + // `respond` drains it first. So a bodyless `curl -X POST …/resolve` + // would apply the change and then hang until the client gave up. + if (request.head.method.requestHasBody() and + request.head.transfer_encoding == .none and + request.head.content_length == null) + { + request.head.content_length = 0; + } + // A request's worth of git output, JSON and parsed bodies, dropped in // one go once the response is on the wire. var arena: std.heap.ArenaAllocator = .init(self.gpa);