cmd/sgai/skel/.sgai/skills/coding-practices/go-http-server-routes/SKILL.md
Go 1.22+ enhanced HTTP routing with method matching and wildcards. When defining HTTP routes using net/http ServeMux in Go 1.22+
npx skillsauth add sandgardenhq/sgai go-http-server-routesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Enhanced routing patterns for net/http.ServeMux introduced in Go 1.22.
Specify HTTP methods directly in patterns:
mux := http.NewServeMux()
mux.HandleFunc("GET /posts/{id}", getPost)
mux.HandleFunc("POST /posts", createPost)
mux.HandleFunc("PUT /posts/{id}", updatePost)
mux.HandleFunc("DELETE /posts/{id}", deletePost)
GET also matches HEAD requests405 Method Not Allowed with Allow header{name}Matches exactly one path segment:
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "User ID: %s", id)
})
{name...}Matches all remaining path segments:
mux.HandleFunc("GET /files/{pathname...}", func(w http.ResponseWriter, r *http.Request) {
pathname := r.PathValue("pathname")
// /files/docs/readme.txt -> pathname = "docs/readme.txt"
})
{$}Matches only the path with trailing slash:
mux.HandleFunc("GET /posts/{$}", listPosts) // matches /posts/ only
mux.HandleFunc("GET /posts/{id}", getPost) // matches /posts/123
| Pattern | Matches | Does Not Match |
|---------|---------|----------------|
| /posts/{$} | /posts/ | /posts, /posts/123 |
| /posts/ | /posts/, /posts/123, /posts/a/b | /posts |
| /posts/{id} | /posts/123 | /posts/, /posts/a/b |
Extract wildcard values from requests:
func getPost(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// use id...
}
For third-party routers to integrate:
r.SetPathValue("id", "123")
The most specific pattern wins. A pattern is more specific if it matches a strict subset of requests.
| Pattern A | Pattern B | Winner | Reason |
|-----------|-----------|--------|--------|
| /posts/latest | /posts/{id} | A | Literal matches fewer requests |
| GET /posts/{id} | /posts/{id} | A | Method constraint is more specific |
| /users/{u}/posts/latest | /users/{u}/posts/{id} | A | Literal segment more specific |
Two patterns conflict if neither is more specific than the other. Registering conflicting patterns causes a panic.
// CONFLICT - will panic
mux.HandleFunc("/posts/{id}", handler1)
mux.HandleFunc("/{resource}/latest", handler2)
// Both match /posts/latest, neither is more specific
Patterns with hosts take precedence over patterns without:
mux.HandleFunc("example.com/", hostHandler) // wins for example.com
mux.HandleFunc("/", defaultHandler) // wins for other hosts
package main
import (
"encoding/json"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/posts", listPosts)
mux.HandleFunc("GET /api/posts/{id}", getPost)
mux.HandleFunc("POST /api/posts", createPost)
mux.HandleFunc("PUT /api/posts/{id}", updatePost)
mux.HandleFunc("DELETE /api/posts/{id}", deletePost)
mux.HandleFunc("GET /static/{filepath...}", serveStatic)
http.ListenAndServe(":8080", mux)
}
func getPost(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
post, err := findPost(id)
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(post)
}
func serveStatic(w http.ResponseWriter, r *http.Request) {
filepath := r.PathValue("filepath")
http.ServeFile(w, r, "static/"+filepath)
}
mux.HandleFunc("GET /api/{resource}", listHandler)
mux.HandleFunc("GET /api/{resource}/{id}", getHandler)
mux.HandleFunc("POST /api/{resource}", createHandler)
mux.HandleFunc("PUT /api/{resource}/{id}", updateHandler)
mux.HandleFunc("DELETE /api/{resource}/{id}", deleteHandler)
mux.HandleFunc("GET /users/{userID}/posts", listUserPosts)
mux.HandleFunc("GET /users/{userID}/posts/{postID}", getUserPost)
mux.HandleFunc("GET /posts/latest", getLatestPost) // more specific
mux.HandleFunc("GET /posts/{id}", getPost) // less specific
For code that relied on pre-1.22 behavior (literal braces in patterns):
GODEBUG=httpmuxgo121=1 ./myapp
/posts/{id} matches all methods{$} - /posts/ matches /posts/anything, use /posts/{$} for exact match{id} and {identifier} are different wildcards, name doesn't affect precedencedocumentation
Start, stop, and steer agentic sessions in sgai workspaces. Use when you need to launch AI agent sessions, halt running sessions, or inject steering instructions to guide the agent mid-execution without stopping it.
development
Monitor sgai workspace status, events, progress, diffs, and workflow diagrams. Use when you need to observe what agents are doing, track progress, get the current state of all workspaces, subscribe to real-time updates via SSE, or inspect code changes.
development
Access agents, skills, and code snippets available in sgai workspaces. Use when you need to discover what agents are defined in a workspace, browse available skills, get skill instructions, find code snippets by language, or retrieve snippet content for a specific task.
data-ai
Handle agent questions and work gates in sgai workspaces. Use when an agent is blocked waiting for human input, when you need to respond to multi-choice questions, approve work gates, or provide free-text answers to agent queries.