Skip to content

Refactor Exceptions - #285

Merged
mark-torres10 merged 10 commits into
mainfrom
refactor_simulation
Mar 25, 2026
Merged

Refactor Exceptions#285
mark-torres10 merged 10 commits into
mainfrom
refactor_simulation

Conversation

@dudu-theman

@dudu-theman dudu-theman commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator

Problem
Having to write try except blocks in the api endpoint functions can become very repetitive as many endpoints end up using the same errors, but with slightly different messages.
Fixes #236.

Solution
Create custom exception handlers to deal with exceptions. Utilize the custom exceptions for this.

Changes

  1. Added a file, simulation/api/exception_handlers.py containing exception handlers for ALL exceptions (not just 500). My reasoning for doing this was that there were so many excepts for each try that it seemed cleaner to just have an exception handler for every single exception instead of still having to deal with a bunch of try excepts.
  2. In the file, I defined a const EXCEPTION_HANDLERS, a dictionary that maps exception to exception handler object.
  3. Changed all of the endpoints to not use a try except and let the server catch the exception itself.
  4. Changed the 500 logging to have the request method and url path instead of a customized, hardcoded message. I think this is a lot cleaner than the previous 500 error message, as it shows much more context.
  5. Added ApiValidationError and ApiInvalidInputError (both ValueErrors) to simulation/api/errors.py to deal with ValueErrors since ValueErrors could either return 400 or 422.
  6. Migrate middleware in simulation/api/main.py from class-based middleware to fully asynchronous. Using the class-based caused us to lose the error message when having custom exception handlers. This is because with try except, the endpoint would return a JSONResponse object in the try except regardless, whereas with custom exception handlers (and no try except blocks) I have to add all of the exceptions handler into FastAPI's ExceptionMiddleware, which catches my Exceptions.
  7. Change the order of middleware to have RequestID be the outermost layer of middleware.
  8. Add a testing file, tests/api/test_exception_handlers.py (which uses the same app object as simulation/api/main.py to preserve the middleware) with fault injection to test all exceptions.

Manual Verification
PYTHONPATH=. uv run pytest tests/api/test_exception_handlers.py -v

Summary by CodeRabbit

  • Refactor

    • Centralized API exception handling with explicit mappings to HTTP statuses (400, 401, 404, 409, 422, 500); removed per-route error wrapping so errors propagate to central handlers.
  • New Features

    • ASGI middleware injects security headers and a request ID into responses.
    • New API error types to distinguish validation vs. invalid-input responses (422 vs. 400).
  • Tests

    • End-to-end tests validating error shapes, status mappings (including 400 for invalid input), and response headers.

@vercel

vercel Bot commented Mar 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui Ready Ready Preview, Comment Mar 25, 2026 1:45am

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Centralizes API error handling by adding two marker exceptions and a new exception_handlers module, moves middleware to pure-ASGI implementations, removes per-route try/except error-to-HTTP conversions (letting exceptions propagate), and adds tests exercising the centralized handlers and middleware headers.

Changes

Cohort / File(s) Summary
Exceptions & handlers
simulation/api/errors.py, simulation/api/exception_handlers.py
Added marker exceptions ApiValidationError, ApiInvalidInputError. Added exception_handlers with FastAPI handlers mapping domain/auth/validation and generic exceptions to JSONResponse; exported EXCEPTION_HANDLERS.
App initialization
simulation/api/main.py
Now registers handlers dynamically from EXCEPTION_HANDLERS; imports ASGI middleware from simulation.api.middleware instead of defining inline handlers/middleware.
ASGI middleware
simulation/api/middleware.py, lib/security_headers.py
Moved SecurityHeaders and RequestId middleware to pure-ASGI in simulation/api/middleware.py; removed the old Starlette-based middleware from lib/security_headers.py.
Routes — removed local error conversion
simulation/api/routes/agents.py, simulation/api/routes/metadata.py, simulation/api/routes/posts.py, simulation/api/routes/runs.py
Deleted per-route try/except blocks that converted exceptions to HTTP error responses and related logging/imports; executor helpers now call services directly and let exceptions propagate to global handlers.
Tests
tests/api/test_exception_handlers.py
Added tests that mount endpoints to trigger each exception handler, assert HTTP status and structured JSON payloads, and verify middleware headers.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant ASGI as ASGI Middleware (SecurityHeaders / RequestId)
  participant App as FastAPI App / Router
  participant Service as Business Service
  participant Handlers as Exception Handlers

  Client->>ASGI: HTTP request
  ASGI->>App: forward scope/receive/send
  App->>Service: invoke route handler / service call
  Service-->>App: raises Exception
  App->>Handlers: dispatch to registered exception handler
  Handlers-->>ASGI: JSONResponse (status + body)
  ASGI-->>Client: HTTP response (headers + body)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Refactor

Poem

🐰 I hopped through code with tiny paws,

Moved try/catch out to simpler laws,
Handlers wait where exceptions fall,
Headers shine on every call,
A joyful thump — error flow, one ball!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Refactor Exceptions' is generic and vague, failing to capture the primary objective of centralizing API exception handling or reducing repetitive try/except blocks. Consider a more descriptive title like 'Centralize exception handling to reduce boilerplate' or 'Refactor API exception handlers for consistency' to better reflect the main change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR substantially addresses all coding requirements from issue #236: centralizes exception handling via dedicated handlers, substantially reduces try/except boilerplate across routes, implements consistent 500 error schema, uses logger.exception() for stack traces, maintains existing error handling for known exceptions (404/409/422), and includes comprehensive tests.
Out of Scope Changes check ✅ Passed All changes directly support the refactoring objectives. Middleware migration from BaseHTTPMiddleware to ASGI middleware, middleware reordering, new exception classes (ApiValidationError/ApiInvalidInputError), and refactored routes all align with centralizing exception handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor_simulation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@simulation/api/exception_handlers.py`:
- Around line 28-35: The global_exception_handler currently defined as def
global_exception_handler(request: Request, exc: Exception) loses traceback
because sync handlers run in a threadpool; change it to an async function (async
def global_exception_handler(...)) so the exception context is preserved and
logger.exception() can capture the full traceback, keeping the same return
(JSONResponse via error_response) and parameters (request, exc) and retaining
the detail logic (is_local_mode()) unchanged.

In `@simulation/api/main.py`:
- Line 23: There are two implementations of the security-headers middleware
causing inconsistency: the ASGI SecurityHeadersMiddleware in this module and the
old BaseHTTPMiddleware variant still in lib.security_headers; keep a single
implementation to avoid accidental re-import of the broken path. Remove the
duplicate middleware here and instead import and use the canonical
SecurityHeadersMiddleware (or move the canonical implementation back into
lib.security_headers), ensure you still import _hsts_enabled from
lib.security_headers, update any app.add_middleware/app.mount calls to reference
the single SecurityHeadersMiddleware symbol, and delete the old
BaseHTTPMiddleware-based class from lib.security_headers (or vice versa) so only
one SecurityHeadersMiddleware implementation exists project-wide.

In `@tests/api/test_exception_handlers.py`:
- Around line 33-97: Add a real RequestValidationError regression by adding an
endpoint that fails during FastAPI request parsing rather than raising inside
the handler body; for example implement a route function named
trigger_request_validation_error (or similar) that declares a typed
path/query/body parameter (e.g., id: int or a Pydantic model) and does nothing
else, so sending an invalid type or malformed/missing body triggers FastAPI's
RequestValidationError before the handler runs; add this alongside the other
trigger routes (refer to existing trigger_* functions) and ensure the test
invokes it with an invalid request to exercise the RequestValidationError
handler (also add corresponding coverage in the later section referenced
109-211).
- Around line 100-102: Don't mutate the global FastAPI app at import: stop
calling app.include_router(_router, prefix="/test-errors") and creating a
module-level TestClient(app, ...) during import. Instead, move the router
registration and TestClient creation into a pytest fixture (e.g., a
function-scoped fixture that registers _router, yields a TestClient, and then on
teardown restores the original app.router.routes and app.openapi_schema); use
app.include_router(_router, prefix="/test-errors") inside the fixture and after
yielding revert app.router.routes back to the saved original list and reset
app.openapi_schema to the saved value so the global simulation.api.main.app
remains unchanged across tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65a6c061-481e-478e-a102-9a397aafd64c

📥 Commits

Reviewing files that changed from the base of the PR and between 36a2ac8 and 8a7dc31.

📒 Files selected for processing (8)
  • simulation/api/errors.py
  • simulation/api/exception_handlers.py
  • simulation/api/main.py
  • simulation/api/routes/agents.py
  • simulation/api/routes/metadata.py
  • simulation/api/routes/posts.py
  • simulation/api/routes/runs.py
  • tests/api/test_exception_handlers.py

Comment thread simulation/api/exception_handlers.py Outdated
Comment thread simulation/api/main.py Outdated
Comment on lines +33 to +97
@_router.get("/trigger/run-not-found")
async def trigger_run_not_found():
raise ApiRunNotFoundError(run_id="run-abc")


@_router.get("/trigger/run-creation-failed")
async def trigger_run_creation_failed():
raise ApiRunCreationFailedError(message="engine returned no run")


@_router.get("/trigger/handle-already-exists")
async def trigger_handle_already_exists():
raise ApiHandleAlreadyExistsError(handle="alice")


@_router.get("/trigger/agent-not-found")
async def trigger_agent_not_found():
raise ApiAgentNotFoundError(handle="alice")


@_router.get("/trigger/target-agent-not-found")
async def trigger_target_agent_not_found():
raise ApiTargetAgentNotFoundError(handle="bob")


@_router.get("/trigger/follow-edge-already-exists")
async def trigger_follow_edge_already_exists():
raise ApiAgentFollowEdgeAlreadyExistsError(
follower_handle="alice", target_handle="bob"
)


@_router.get("/trigger/follow-edge-not-found")
async def trigger_follow_edge_not_found():
raise ApiAgentFollowEdgeNotFoundError(follower_handle="alice", target_handle="bob")


@_router.get("/trigger/self-follow-not-allowed")
async def trigger_self_follow_not_allowed():
raise ApiSelfFollowNotAllowedError(handle="alice")


@_router.get("/trigger/unauthorized")
async def trigger_unauthorized():
raise UnauthorizedError(message="Token expired")


@_router.get("/trigger/value-error")
async def trigger_value_error():
raise ValueError("generic value error")


@_router.get("/trigger/api-validation-error")
async def trigger_api_validation_error():
raise ApiValidationError("specific validation failed")


@_router.get("/trigger/api-invalid-input")
async def trigger_api_invalid_input():
raise ApiInvalidInputError("malformed ID format")


@_router.get("/trigger/internal-error")
async def trigger_internal_error():
raise RuntimeError("unexpected crash")

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.

🧹 Nitpick | 🔵 Trivial

Add a real RequestValidationError regression.

simulation/api/exception_handlers.py also replaces FastAPI's RequestValidationError handler, but every test here raises from inside the endpoint body. A broken registration or response shape for request-parsing failures would currently go unnoticed.

Also applies to: 109-211

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/api/test_exception_handlers.py` around lines 33 - 97, Add a real
RequestValidationError regression by adding an endpoint that fails during
FastAPI request parsing rather than raising inside the handler body; for example
implement a route function named trigger_request_validation_error (or similar)
that declares a typed path/query/body parameter (e.g., id: int or a Pydantic
model) and does nothing else, so sending an invalid type or malformed/missing
body triggers FastAPI's RequestValidationError before the handler runs; add this
alongside the other trigger routes (refer to existing trigger_* functions) and
ensure the test invokes it with an invalid request to exercise the
RequestValidationError handler (also add corresponding coverage in the later
section referenced 109-211).

Comment thread tests/api/test_exception_handlers.py Outdated
@dudu-theman

dudu-theman commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator Author

I ended up refactoring a lot more than the original issue description (which ended up being a pretty comprehensive refactor), so I'll document my whole process:

  1. Familiarize myself with all of the api routes, and find out where the repeated work was.
  2. Found out that 500 errors are returned for all sorts of reasons, but this can be solved best with just including the url path and the method type, and using logger.exception() for a full traceback.
  3. Found out that there are a bunch of other exceptions that aren't just 500 errors, and would still have to potentially use try catch block or manually raise exceptions inside of the api function.
  4. Decided that it would be best to just put all of the exceptions into a single file, and researched into how exceptions, exception handling work and how to implement custom exception handlers.
  5. Found out that the built-in FastAPI ExceptionMiddleware searches through all of the ExceptionHandlers (that I have to manually add) and finds the matching Exception. Falls back to basic Exception if no match.
  6. Refactor all of the code to not use try except, and instead just return Exception when there is a failure and Response object when successful.
  7. Discover that FastAPI's class-based middleware (BaseHTTPMiddleware) wraps the exception into an ExceptionGroup object, and then when the Exception is passed to FastAPI ExceptionMiddleware, it falls back into basic Exception handling. This is a problem as all exceptions ended up falling into basic Exception handling regardless of the exception type.
  8. The solution for this was to migrate to fully ASGI middleware to preserve the specific Exception type.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
simulation/api/exception_handlers.py (1)

148-155: 🧹 Nitpick | 🔵 Trivial

Remove redundant ApiValidationError check.

The explicit ApiValidationError branch (lines 151-154) returns the same response as the fallback (line 155). Consider simplifying:

♻️ Proposed simplification
 def value_error_handler(_request: Request, exc: ValueError) -> JSONResponse:
     if isinstance(exc, ApiInvalidInputError):
         return error_response(status_code=400, code="INVALID_INPUT", message=str(exc))
-    if isinstance(exc, ApiValidationError):
-        return error_response(
-            status_code=422, code="VALIDATION_ERROR", message=str(exc)
-        )
+    # ApiValidationError and other ValueError subclasses → 422
     return error_response(status_code=422, code="VALIDATION_ERROR", message=str(exc))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulation/api/exception_handlers.py` around lines 148 - 155, In
value_error_handler, remove the redundant explicit ApiValidationError branch and
let the final fallback handle it; keep the ApiInvalidInputError check (returning
INVALID_INPUT via error_response) and then return the generic VALIDATION_ERROR
fallback using error_response for all other ValueError types (including
ApiValidationError) so you can delete the if isinstance(exc, ApiValidationError)
block and its return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@simulation/api/exception_handlers.py`:
- Around line 158-171: The EXCEPTION_HANDLERS mapping currently types values as
object which loses handler signatures; update the annotation for
EXCEPTION_HANDLERS to a specific callable type (e.g., use typing.Callable with
appropriate parameters/return like Callable[[Request, Exception], Response] or
define an ExceptionHandler alias) and apply it to the mapping so entries such as
global_exception_handler, value_error_handler, validation_exception_handler,
unauthorized_handler, run_not_found_handler, run_creation_failed_handler,
handle_already_exists_handler, agent_not_found_handler,
target_agent_not_found_handler, follow_edge_already_exists_handler,
follow_edge_not_found_handler, and self_follow_not_allowed_handler are typed
correctly for IDE/type-checker support.

---

Duplicate comments:
In `@simulation/api/exception_handlers.py`:
- Around line 148-155: In value_error_handler, remove the redundant explicit
ApiValidationError branch and let the final fallback handle it; keep the
ApiInvalidInputError check (returning INVALID_INPUT via error_response) and then
return the generic VALIDATION_ERROR fallback using error_response for all other
ValueError types (including ApiValidationError) so you can delete the if
isinstance(exc, ApiValidationError) block and its return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 385f10bd-db23-495a-b722-320d0014f639

📥 Commits

Reviewing files that changed from the base of the PR and between 8a7dc31 and 6c45bb9.

📒 Files selected for processing (1)
  • simulation/api/exception_handlers.py

Comment on lines +158 to +171
EXCEPTION_HANDLERS: dict[type[Exception], object] = {
Exception: global_exception_handler,
ValueError: value_error_handler,
RequestValidationError: validation_exception_handler,
UnauthorizedError: unauthorized_handler,
ApiRunNotFoundError: run_not_found_handler,
ApiRunCreationFailedError: run_creation_failed_handler,
ApiHandleAlreadyExistsError: handle_already_exists_handler,
ApiAgentNotFoundError: agent_not_found_handler,
ApiTargetAgentNotFoundError: target_agent_not_found_handler,
ApiAgentFollowEdgeAlreadyExistsError: follow_edge_already_exists_handler,
ApiAgentFollowEdgeNotFoundError: follow_edge_not_found_handler,
ApiSelfFollowNotAllowedError: self_follow_not_allowed_handler,
}

@coderabbitai coderabbitai Bot Mar 23, 2026

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.

🧹 Nitpick | 🔵 Trivial

Consider a more specific type annotation for handler mapping.

Using object for handler values loses type information. A more precise type would improve IDE support and type checking:

♻️ Proposed type improvement
+from collections.abc import Awaitable, Callable
+from typing import TypeAlias
+
+ExceptionHandler: TypeAlias = Callable[
+    [Request, Exception], JSONResponse | Awaitable[JSONResponse]
+]
+
-EXCEPTION_HANDLERS: dict[type[Exception], object] = {
+EXCEPTION_HANDLERS: dict[type[Exception], ExceptionHandler] = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulation/api/exception_handlers.py` around lines 158 - 171, The
EXCEPTION_HANDLERS mapping currently types values as object which loses handler
signatures; update the annotation for EXCEPTION_HANDLERS to a specific callable
type (e.g., use typing.Callable with appropriate parameters/return like
Callable[[Request, Exception], Response] or define an ExceptionHandler alias)
and apply it to the mapping so entries such as global_exception_handler,
value_error_handler, validation_exception_handler, unauthorized_handler,
run_not_found_handler, run_creation_failed_handler,
handle_already_exists_handler, agent_not_found_handler,
target_agent_not_found_handler, follow_edge_already_exists_handler,
follow_edge_not_found_handler, and self_follow_not_allowed_handler are typed
correctly for IDE/type-checker support.

@mark-torres10 mark-torres10 Mar 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CodeRabbit's comment is a bit nitpicky, OK to keep as is.

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.

@mark-torres10, understood — no worries, I'll drop this one! 🐇

(´・ω・`)


🧠 Learnings used
Learnt from: mark-torres10
Repo: METResearchGroup/social_agent_simulation_platform PR: 172
File: simulation/core/models/posts.py:0-0
Timestamp: 2026-03-08T16:24:56.751Z
Learning: Guideline: Rename Post model field from uri to source_id across the codebase. Apply to all layers impacted: update canonical_post_id parameter, create a new Alembic migration to rename the DB column, adjust adapter SQL, update API schemas (PostSchema) where this is a breaking change, migrate UI types to camelCase (sourceId), rename validators referencing uri, and update all factories and tests accordingly. Do not allow post.uri in reviews going forward; the field now is post.source_id. This change affects Python files under simulation/, SQL migrations, and any API/UI/validation code that references the field.

@mark-torres10 mark-torres10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

few comments, but otherwise great work! Will merge once those are addressed.

Comment thread simulation/api/main.py Outdated
title="Agent Simulation Platform API",
lifespan=lifespan,
)
class SecurityHeadersMiddleware:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could you delete the SecurityHeadersMiddleware to the lib/security_headers.py file as we don't use it anymore, and then move this SecurityHeadersMIiddleware and the RequestIdMiddleware classes to a simulation/api/middleware.py file?

Comment thread simulation/api/main.py
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler) # type: ignore[reportArgumentType]

for exc_class, handler_func in EXCEPTION_HANDLERS.items():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great job on defining the exception handlers in EXCEPTION_HANDLERS and just looping through it, you're right in that this greatly cleans up the number of lines and logic.

return error_response(status_code=422, code="VALIDATION_ERROR", message=str(exc))


EXCEPTION_HANDLERS: dict[type[Exception], object] = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If I'm understanding this correctly, defining these exception handlers will mean something like:

  1. The application throws an error.
  2. The exception is caught by the application.
  3. Based on the exception raised, the appropriate handler is thrown.

Is that correct? Just want to make sure that I follow the intention based on the code and your description.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yep, exactly that! The exception is caught by FastAPI's ExceptionMiddleware, which will match the Exception thrown to one of the exceptions added to the middleware (if it exists) and falls back to the basic Exception handler when it doesn't match any.

Comment on lines +158 to +171
EXCEPTION_HANDLERS: dict[type[Exception], object] = {
Exception: global_exception_handler,
ValueError: value_error_handler,
RequestValidationError: validation_exception_handler,
UnauthorizedError: unauthorized_handler,
ApiRunNotFoundError: run_not_found_handler,
ApiRunCreationFailedError: run_creation_failed_handler,
ApiHandleAlreadyExistsError: handle_already_exists_handler,
ApiAgentNotFoundError: agent_not_found_handler,
ApiTargetAgentNotFoundError: target_agent_not_found_handler,
ApiAgentFollowEdgeAlreadyExistsError: follow_edge_already_exists_handler,
ApiAgentFollowEdgeNotFoundError: follow_edge_not_found_handler,
ApiSelfFollowNotAllowedError: self_follow_not_allowed_handler,
}

@mark-torres10 mark-torres10 Mar 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CodeRabbit's comment is a bit nitpicky, OK to keep as is.

@mark-torres10

Copy link
Copy Markdown
Collaborator

could you also merge the latest main into this? That'll merge in some tests that will verify that this implementation will work even in production.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@simulation/api/middleware.py`:
- Around line 48-60: The middleware currently accepts client-supplied
X-Request-ID verbatim (request_id) and echoes/logs it via log_request_start and
send_with_request_id; harden this by validating/sanitizing request_id after
reading request.headers.get("X-Request-ID"): enforce an allowed charset (e.g.,
hex or printable ASCII), strip/control character check, and cap length (e.g., 64
chars); if validation fails, replace with a freshly generated id
(uuid.uuid4().hex), set request.state.request_id to the final safe value, and
use that sanitized id for log_request_start and when adding the X-Request-ID
header in send_with_request_id so only validated IDs are logged/echoed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 66b208bb-daaa-45d8-a321-fbd7ff99a3c4

📥 Commits

Reviewing files that changed from the base of the PR and between 915b1e6 and f30f287.

📒 Files selected for processing (3)
  • lib/security_headers.py
  • simulation/api/main.py
  • simulation/api/middleware.py
💤 Files with no reviewable changes (1)
  • lib/security_headers.py

Comment on lines +48 to +60
request_id = request.headers.get("X-Request-ID") or uuid.uuid4().hex
request.state.request_id = request_id
log_request_start(
request_id=request_id,
method=request.method,
path=request.url.path,
)

async def send_with_request_id(message: Message) -> None:
if message["type"] == "http.response.start":
headers = MutableHeaders(scope=message)
headers["X-Request-ID"] = request_id
await send(message)

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.

🧹 Nitpick | 🔵 Trivial

Harden inbound X-Request-ID before echoing/logging.

The current flow trusts client-supplied X-Request-ID verbatim. Consider validating charset/length and regenerating when invalid to avoid malformed or high-cardinality IDs in logs/headers.

Suggested patch
 import uuid
+import re
@@
 from lib.request_logging import log_request_start
 from lib.security_headers import _hsts_enabled
 
+REQUEST_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,128}$")
@@
-        request_id = request.headers.get("X-Request-ID") or uuid.uuid4().hex
+        incoming_request_id = request.headers.get("X-Request-ID")
+        request_id = (
+            incoming_request_id
+            if incoming_request_id and REQUEST_ID_RE.fullmatch(incoming_request_id)
+            else uuid.uuid4().hex
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@simulation/api/middleware.py` around lines 48 - 60, The middleware currently
accepts client-supplied X-Request-ID verbatim (request_id) and echoes/logs it
via log_request_start and send_with_request_id; harden this by
validating/sanitizing request_id after reading
request.headers.get("X-Request-ID"): enforce an allowed charset (e.g., hex or
printable ASCII), strip/control character check, and cap length (e.g., 64
chars); if validation fails, replace with a freshly generated id
(uuid.uuid4().hex), set request.state.request_id to the final safe value, and
use that sanitized id for log_request_start and when adding the X-Request-ID
header in send_with_request_id so only validated IDs are logged/echoed.

@mark-torres10

Copy link
Copy Markdown
Collaborator

LGTM! Great work @dudu-theman !

@mark-torres10
mark-torres10 merged commit a5fb808 into main Mar 25, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Centralize unexpected exception -> 500 handling for simulation API routes

2 participants