Skip to content

fix(acp): include the relay response body in REST error messages - #6227

Open
Joxyko wants to merge 1 commit into
block:mainfrom
Joxyko:t37/acp-http-body
Open

fix(acp): include the relay response body in REST error messages#6227
Joxyko wants to merge 1 commit into
block:mainfrom
Joxyko:t37/acp-http-body

Conversation

@Joxyko

@Joxyko Joxyko commented Aug 18, 2026

Copy link
Copy Markdown

Summary

RestClient::request_with_retry builds its error from the status line alone and drops the response body:

return Err(RelayError::Http(format!(
    "{method} {} returned HTTP {}", path, resp.status()
)));

The relay explains which rule refused the request in that body — restricted: agent-turn-metric p tag must be the registered owner of this agent, restricted: not a relay member, and so on. The client discards it at the moment it arrives, so every caller upstack sees a bare 403 and has no way to tell an ownership problem from a membership problem from a rate-limit rule.

Concretely: a 403 on POST /events that blocked NIP-AM turn-metric publishing was unreadable from the client side for days. The reason was being sent correctly by the relay the whole time — the status code is the only thing that survived the client.

This appends the body to the message, bounded, on both the terminal arm and the retriable arm (last_err, which is what surfaces once retries are exhausted).

Behaviour when there is no body is unchangedformat_error_body returns an empty string, so the message is byte-for-byte what it was before. That is covered by a test, since it is the property most likely to be broken by a later edit.

Two things a reviewer will reasonably ask about, both handled:

  • Unbounded bodies. This path is reached precisely when the peer is misbehaving, so the read itself is capped at 512 bytes via resp.chunk() rather than resp.text() — a failing request must not become a memory amplifier.
  • Log injection. Control characters are collapsed to spaces, so a body cannot break one log line into several or forge a log entry. Truncation walks back to a UTF-8 char boundary.

Related issue

None found. Searched open PRs and issues touching crates/buzz-acp/src/relay.rs; the nearest neighbours are #5724 (honour the relay's retry hint on a rate-limited REST call) and #4912 (rate-limit overhaul … client retry fixes), which both change RestClient for other reasons — I checked their diffs and neither touches the Ok(resp) arms of request_with_retry, so this should not conflict. #4821 (recognize the membership gate's bodyless 404 in onboarding) is adjacent in spirit but is desktop-side and about a response that has no body at all.

Testing

  • cargo test -p buzz-acp --lib797 passed, 0 failed (792 before, +5 new).
  • cargo clippy -p buzz-acp --all-targets -- -D warnings → clean.
  • cargo fmt -p buzz-acp -- --check → clean.
  • Toolchain 1.95.0, the version pinned in rust-toolchain.toml.

New unit tests cover: empty body (message unchanged), the relay's real restricted: … reason coming through, newline flattening, the length bound, and a multi-byte character straddling the cap.

`request_with_retry` built its error from the status line alone and dropped
the body, where the relay explains which rule refused the request
(`restricted: ...`). Every caller upstack saw a bare status code.

Append the body to the message on both the terminal arm and the retriable
arm (`last_err`, which is what surfaces once retries are exhausted).

The read is bounded at 512 bytes via `resp.chunk()` rather than `text()`:
this path is reached precisely when the peer is misbehaving, so a failing
request must not become a memory amplifier. Control characters are collapsed
so a body cannot forge or split a log line, and truncation walks back to a
UTF-8 boundary.

A bodyless failure produces the exact message it did before; that property
has its own test, since it is the one most likely to be broken later.

Signed-off-by: VSCteam <jose.cortinat@valuestrategyconsulting.com>
@Joxyko
Joxyko requested a review from a team as a code owner August 18, 2026 15:11
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