📝 Description
When a middleware replaces the request using r.WithContext(newCtx) after routing has already matched a route (for example, inline middleware on a sub-router or route group), downstream handlers can intermittently lose access to URL parameters stored in Chi's routing context.
The issue occurs because the routing context (chi.Context) attached to the original request is not consistently preserved or propagated across the cloned request in certain middleware chains. As a result, calls to chi.URLParam(r, "param") or chi.RouteContext(r.Context()) return empty values or nil even though the route was successfully matched. This behavior is particularly prevalent when middleware performs request cloning after routing and before the handler executes, or when context pooling (sync.Pool) prematurely reclaims the routing context.
🎯 Acceptance Criteria
🛠️ Technical Specifications & Context
In RoseMark45/chi, routing context is managed via chi.Context and stored in the request context under the private RouteCtxKey.
Key Areas of Interest:
context.go:
- Inspect
RouteContext(ctx context.Context) *Context. If a middleware creates a new context that does not inherit from the request's original context (or if the context chain is structured such that the key is shadowed), RouteContext will fail to retrieve the active routing context.
mux.go:
- Look at the
ServeHTTP and routing dispatch logic where chi.Context is fetched from the sync.Pool and subsequently put back using defer mx.pool.Put(rctx).
- If a middleware replaces the request context, the reference to the original context might be lost, or the lifecycle of the pooled context might be cut short, leading to intermittent nil-pointer dereferences or cleared parameters when the pool reclaims the object.
Proposed Solution Directions:
- Ensure that if a middleware calls
r.WithContext(newCtx), chi provides a robust way to retrieve the routing context, or ensure that chi's internal handlers can recover the routing context from the parent context chain.
- If the user derives
newCtx from r.Context(), verify that the pointer to chi.Context is not being mutated or reset prematurely by nested routing logic.
- Add defensive checks in
RouteContext() to handle cases where the context has been wrapped.
🧪 Verification & Testing
Create a test case in mux_test.go (or a new test file) that reproduces the issue:
func TestMiddlewareRxCloneURLParams(t *testing.T) {
r := chi.NewRouter()
r.Route("/users/{userID}", func(r chi.Router) {
// Middleware that clones the request context
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Simulate context replacement (e.g., adding a tracing ID or user session)
ctx := context.WithValue(req.Context(), "customKey", "customValue")
next.ServeHTTP(w, req.WithContext(ctx))
})
})
r.Get("/profile", func(w http.ResponseWriter, req *http.Request) {
userID := chi.URLParam(req, "userID")
if userID != "123" {
t.Errorf("expected userID to be '123', got '%s'", userID)
}
w.Write([]byte("ok"))
})
})
ts := httptest.NewServer(r)
defer ts.Close()
res, err := http.Get(ts.URL + "/users/123/profile")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("expected status OK, got %d", res.StatusCode)
}
}
Run the tests using:
go test -v -run=TestMiddlewareRxCloneURLParams ./...

This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #1 either in the PR description or in a PR's comment.
🪙 Also, everyone can tip any user commenting /tip 20 @RoseMark45 (replace 20 with the amount, and @RoseMark45 with the user to tip).
📖 If you want to learn more, check out our documentation.
📝 Description
When a middleware replaces the request using
r.WithContext(newCtx)after routing has already matched a route (for example, inline middleware on a sub-router or route group), downstream handlers can intermittently lose access to URL parameters stored in Chi's routing context.The issue occurs because the routing context (
chi.Context) attached to the original request is not consistently preserved or propagated across the cloned request in certain middleware chains. As a result, calls tochi.URLParam(r, "param")orchi.RouteContext(r.Context())return empty values ornileven though the route was successfully matched. This behavior is particularly prevalent when middleware performs request cloning after routing and before the handler executes, or when context pooling (sync.Pool) prematurely reclaims the routing context.🎯 Acceptance Criteria
chi.URLParam(r, "param")must consistently return the correct route parameters in downstream handlers, even if intermediate middleware clones or replaces the request context usingr.WithContext().chi.RouteContext(r.Context())must correctly resolve to the active routing context after request cloning.chi.Contextback to thesync.Poolwhen cloned requests/contexts are still referencing it.🛠️ Technical Specifications & Context
In
RoseMark45/chi, routing context is managed viachi.Contextand stored in the request context under the privateRouteCtxKey.Key Areas of Interest:
context.go:RouteContext(ctx context.Context) *Context. If a middleware creates a new context that does not inherit from the request's original context (or if the context chain is structured such that the key is shadowed),RouteContextwill fail to retrieve the active routing context.mux.go:ServeHTTPand routing dispatch logic wherechi.Contextis fetched from thesync.Pooland subsequently put back usingdefer mx.pool.Put(rctx).Proposed Solution Directions:
r.WithContext(newCtx),chiprovides a robust way to retrieve the routing context, or ensure thatchi's internal handlers can recover the routing context from the parent context chain.newCtxfromr.Context(), verify that the pointer tochi.Contextis not being mutated or reset prematurely by nested routing logic.RouteContext()to handle cases where the context has been wrapped.🧪 Verification & Testing
Create a test case in
mux_test.go(or a new test file) that reproduces the issue:Run the tests using:
go test -v -run=TestMiddlewareRxCloneURLParams ./...This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting
/reward 100(replace100with the amount).🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment
/tryto let everyone know!🙌 And when they open the PR, they can comment
/claim #1either in the PR description or in a PR's comment.🪙 Also, everyone can tip any user commenting
/tip 20 @RoseMark45(replace20with the amount, and@RoseMark45with the user to tip).📖 If you want to learn more, check out our documentation.