Skip to content

feat(api): map timeout_ms exhaustion to 504 request_timeout (builds on #40) - #43

Open
tyejcoleman wants to merge 2 commits into
mainfrom
fix/request-timeout-504-mapping
Open

feat(api): map timeout_ms exhaustion to 504 request_timeout (builds on #40)#43
tyejcoleman wants to merge 2 commits into
mainfrom
fix/request-timeout-504-mapping

Conversation

@tyejcoleman

Copy link
Copy Markdown
Contributor

Summary

  • Builds on feat(api): support timeout_ms on remember and search #40 (timeout_ms on /remember and /search).
  • Maps request-scoped timeout exhaustion to 504 Gateway Timeout with stable code: request_timeout, retryable: true.
  • Maps context.Canceled to log-only (no response written — client is gone).
  • Adds handler tests for wrapped, string-fallback, and canceled-no-write cases.

Why

Review on #40 requested this: before this change, hitting the caller-supplied timeout_ms degraded into a generic 500 via writeInternalErrorWithContext. Clients couldn't distinguish "my budget was too tight" from "the engine is broken" and had no stable retry signal.

Harness spec amendment: 20-contracts/http-api.bridge.md gets a 504 row in the status table and a stable-error-codes table entry for request_timeout. See companion commit in keyoku-harness.

Validation

  • go test ./cmd/keyoku-server/... -run TestWriteInternalError -v — all 6 tests pass including the three new ones (DeadlineExceededMapsTo504, DeadlineExceededStringFallback, CanceledWritesNoResponse).
  • Pre-existing TestHandleHeartbeatContext_* failures on main are unrelated (reproduce on untouched main).

Supersedes

Closes #40 (this branch includes moltar-bot's commit plus the requested fix).

Test plan

  • Unit: deadline wrapped via fmt.Errorf("...: %w", context.DeadlineExceeded) → 504
  • Unit: deadline surfaced as raw string "context deadline exceeded" → 504
  • Unit: context.Canceled → no body, default 200 status (header not written)
  • Manual: hit /api/v1/remember with timeout_ms=1000 against Ollama + qwen2.5:7b repeated same-entity; confirm 504 with code=request_timeout

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings April 24, 2026 04:33
@github-actions

github-actions Bot commented Apr 24, 2026

Copy link
Copy Markdown

Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement.

To sign, please comment on this PR with the following text:

I have read the CLA Document and I hereby sign the CLA


I have read the CLA Document and I hereby sign the CLA


1 out of 2 committers have signed the CLA.
✅ (tyejcoleman)[https://github.com/tyejcoleman]
@moltar-bot
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the HTTP API’s error semantics around request-scoped timeouts (timeout_ms) by mapping timeout exhaustion to a stable, retryable 504 error response, and by avoiding response writes when the request context is canceled (client disconnect).

Changes:

  • Add request-scoped context timeouts for /remember and /search using timeout_ms (clamped to 1s..300s).
  • Map context.DeadlineExceeded (including wrapped and string-fallback forms) to HTTP 504 with stable code: request_timeout and retryable: true.
  • Add unit tests covering wrapped deadline exceeded, string fallback, and canceled-no-write behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
cmd/keyoku-server/handlers_memory.go Applies clamped request-scoped timeouts to Remember/Search via context.WithTimeout.
cmd/keyoku-server/handlers.go Adds deadline/cancel detection and maps deadline exhaustion to 504 with stable error code.
cmd/keyoku-server/handlers_errors_test.go Adds tests verifying 504 mapping behavior and cancel no-write behavior.

if errors.Is(err, context.Canceled) {
return true
}
return strings.Contains(err.Error(), "context canceled")

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

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

isCanceled uses strings.Contains(err.Error(), "context canceled"). That can misclassify unrelated errors that merely include this substring, and (via the early return in writeInternalErrorWithContext) can lead to an empty/default 200 response. Consider restricting cancellation detection to errors.Is(err, context.Canceled) (or an exact-match fallback) to avoid swallowing real failures.

Suggested change
return strings.Contains(err.Error(), "context canceled")
return err != nil && err.Error() == "context canceled"

Copilot uses AI. Check for mistakes.
@tyejcoleman
tyejcoleman force-pushed the fix/request-timeout-504-mapping branch from 7ed4481 to 5070daa Compare April 24, 2026 04:57
Builds on #40. When a request-scoped timeout (from client-supplied
timeout_ms or an upstream provider deadline) fires, the handler path
previously unwrapped to writeInternalErrorWithContext and surfaced a
generic 500. Since this endpoint now exposes a caller-controlled budget,
budget exhaustion needs a distinct, retryable signal so clients can
separate "my budget was too tight" from "the engine is broken".

- errors.Is(err, context.DeadlineExceeded) -> 504 with
  code=request_timeout, retryable=true. String fallback covers provider
  SDKs that surface "context deadline exceeded" without wrapping.
- errors.Is(err, context.Canceled) -> log and return without writing,
  since the client socket is gone.
- Tests cover both wrapped and string-fallback forms plus the canceled
  no-write case.
@tyejcoleman
tyejcoleman force-pushed the fix/request-timeout-504-mapping branch from 5070daa to d38bf26 Compare April 24, 2026 05:02
@tyejcoleman

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@tyejcoleman

Copy link
Copy Markdown
Contributor Author

@moltar-bot — this PR builds on your commit from #40, so the CLA check wants your signature too. Could you comment:

I have read the CLA Document and I hereby sign the CLA

Once you do, the check re-runs automatically and this can merge. Thanks!

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.

3 participants