Bug Fix with /quick
A step-by-step example of diagnosing and fixing a bug using CQ's quick workflow.
Scenario
Your team reports that the authentication middleware returns HTTP 500 instead of 401 when a JWT token is expired. The error appears in logs as:
ERROR: token parse failed: token is expired by 2h30m0s
panic: runtime error: index out of range [0] with length 0You need to:
- Reproduce the issue
- Fix the panic and return a proper 401
- Submit with validation passing
Step 1: Start a Quick Task
/quick "fix: auth middleware panics on expired JWT — should return 401"Output:
Task created: T-042
Title: fix: auth middleware panics on expired JWT — should return 401
Scope: auto-detected (go)
Status: in_progressThe task is claimed immediately. You don't need /claim separately.
Step 2: Reproduce the Bug
Write a test that triggers the panic before touching any production code:
// internal/middleware/auth_test.go
func TestAuthMiddleware_ExpiredToken_Returns401(t *testing.T) {
// Generate an already-expired token
token := generateTestToken(t, time.Now().Add(-3*time.Hour))
req := httptest.NewRequest("GET", "/api/data", nil)
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
handler := AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Errorf("expected 401, got %d", rec.Code)
}
}Run it:
cd c4-core && go test ./internal/middleware/... -run TestAuthMiddleware_ExpiredToken--- FAIL: TestAuthMiddleware_ExpiredToken_Returns401 (0.00s)
panic: runtime error: index out of range [0] with length 0 [recovered]
goroutine 7 [running]:
...
FAIL github.com/pilab/cq/internal/middleware [build failed]Bug confirmed.
Step 3: Find the Root Cause
Open internal/middleware/auth.go. Look for where the token is parsed:
// BEFORE — buggy code
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.Header.Get("Authorization"), " ")
tokenStr := parts[1] // panics if header is missing or malformed
claims, err := parseJWT(tokenStr)
if err != nil {
// BUG: err is swallowed, execution continues with nil claims
}
userID := claims.UserID // panics when claims is nil
ctx := context.WithValue(r.Context(), userIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Two bugs:
- No bounds check on
parts— panics if theAuthorizationheader is missing or has no space err != nilblock doesn't return — execution falls through toclaims.UserIDon nil pointer
Step 4: Fix the Code
// AFTER — fixed
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
claims, err := parseJWT(parts[1])
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userIDKey, claims.UserID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Key changes:
SplitN(..., 2)limits splits and keeps the token intact if it contains spaces- Explicit
returnafter each error path - No more nil pointer dereference
Step 5: Run the Test
cd c4-core && go test ./internal/middleware/... -run TestAuthMiddleware_ExpiredToken -v=== RUN TestAuthMiddleware_ExpiredToken_Returns401
--- PASS: TestAuthMiddleware_ExpiredToken_Returns401 (0.00s)
PASS
ok github.com/pilab/cq/internal/middleware 0.012sAlso add a test for the missing-header case:
func TestAuthMiddleware_MissingHeader_Returns401(t *testing.T) {
req := httptest.NewRequest("GET", "/api/data", nil)
// No Authorization header
rec := httptest.NewRecorder()
AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})).ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Errorf("expected 401, got %d", rec.Code)
}
}go test ./internal/middleware/... -v
# All tests passStep 6: Validate
/validateRunning validations...
go-build: PASS
go-vet: PASS
go-test: PASS (42 tests, 0.8s)
All validations passed.Step 7: Submit
/submitSubmitting T-042...
Commit: f3a9b21 "fix(auth): return 401 on expired/missing JWT, prevent panic"
Validation: all passed
Status: done
Task T-042 completed.Step 8: Confirm
/statusDone:
T-042 fix: auth middleware panics on expired JWT f3a9b21Key Patterns
Always reproduce before fixing. Writing a failing test first proves the bug exists and prevents future regressions.
Fix one bug at a time. Two separate fixes (bounds check + error return) are fine in one commit when they affect the same function and address the same root cause.
Validate all test paths. CQ's /validate runs the full test suite. A passing test for the fixed case isn't enough — the entire suite must stay green.
Next Steps
- Multiple files, clearer requirements: Feature Planning
- Complex investigation: Run
/swarm --investigatewhen the root cause is unknown - Usage decision tree: Usage Guide §1