Working with the Navigator Go submodule for web server fixes and enhancements. Use when deployment plans require Navigator changes, config parsing issues arise, or new routing/proxy behavior is needed.
Navigator is included as a Git submodule because showcase routinely needs Navigator fixes to implement deployment plans. Changes are tested in showcase context before being pushed upstream.
Location: navigator/ (Git submodule)
Language: Go
Purpose: Multi-tenant web server with framework independence
navigator/
├── cmd/navigator/ # Production Navigator implementation
├── internal/ # Modular packages
│ ├── config/ # Configuration loading and parsing
│ ├── server/ # HTTP handling, routing, static files
│ ├── auth/ # Authentication (htpasswd)
│ ├── process/ # Web app lifecycle management
│ └── proxy/ # Reverse proxy and Fly-Replay
└── docs/ # MkDocs documentation
Understanding this flow is essential for fixing config-related bugs:
types.go (mirrors YAML structure with yaml tags)parser.go (converts YAML to internal format)types.go (optimized internal representation)Common Bug Pattern: When adding config fields, developers often:
Config struct (internal)YAMLConfig structparser.go// From internal/server/handler.go ServeHTTP()
1. Health checks // BEFORE everything (bypasses auth)
2. Authentication // EARLY check (before routing)
3. Rewrites/redirects
4. CGI scripts
5. Reverse proxies
6. Static files
7. Maintenance mode
8. Web app proxy // Tenant routing
Security Critical: Health checks MUST come before auth. Auth MUST come before tenant routing.
Example: Health Check Config (Recent Fix)
Step 1: Add to YAMLConfig in internal/config/types.go:
type YAMLConfig struct {
Server struct {
// ... other fields
HealthCheck HealthCheckConfig `yaml:"health_check"` // ← Must have yaml tag
} `yaml:"server"`
}
Step 2: Add to internal Config (if different structure needed):
type Config struct {
Server struct {
// ... other fields
HealthCheck HealthCheckConfig
}
}
Step 3: Add parsing logic in internal/config/parser.go:
func (p *ConfigParser) parseServerConfig() {
// ... other parsing
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck // ← CRITICAL
}
Step 4: Add tests in internal/config/parser_test.go:
func TestConfigParser_ParseHealthCheck(t *testing.T) {
yamlConfig := YAMLConfig{}
yamlConfig.Server.HealthCheck = HealthCheckConfig{
Path: "/up",
Response: &HealthCheckResponse{Status: 200, Body: "OK"},
}
parser := NewConfigParser(&yamlConfig)
config, err := parser.Parse()
if config.Server.HealthCheck.Path != "/up" {
t.Errorf("HealthCheck not parsed correctly")
}
}
Step 5: Add integration tests in internal/server/handler_test.go (or appropriate file).
Example: Health Check Handler (Recent Fix)
// Add early in ServeHTTP - order matters!
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Health checks BEFORE authentication
if h.config.Server.HealthCheck.Path != "" && r.URL.Path == h.config.Server.HealthCheck.Path {
h.handleHealthCheck(recorder, r)
return // Stop processing
}
// Authentication check comes next
// ... rest of handler
}
Integration Test (Security Critical):
func TestHandler_ServeHTTP_HealthCheckBeforeAuth(t *testing.T) {
cfg := &config.Config{}
cfg.Server.HealthCheck = config.HealthCheckConfig{
Path: "/up",
Response: &config.HealthCheckResponse{Status: 200, Body: "OK"},
}
cfg.Auth.Enabled = true // Enable auth
basicAuth := &auth.BasicAuth{}
handler := CreateTestHandler(cfg, nil, basicAuth, nil)
req := httptest.NewRequest("GET", "/up", nil)
// NO auth credentials provided
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
// MUST succeed without auth - critical security requirement
if recorder.Code != 200 {
t.Errorf("Health check should bypass auth")
}
}
[ ] Config parser tests (internal/config/parser_test.go)
[ ] Integration tests (appropriate *_test.go)
ServeHTTP# All tests
go test ./...
# Specific package
go test -v ./internal/config/
go test -v ./internal/server/
# With coverage
go test -cover ./...
# Pre-commit validation (CI requirements)
gofmt -s -l . && \
golangci-lint run && \
go vet ./... && \
go test -race -cover -timeout=3m ./... && \
go build ./cmd/navigator-refactored && \
echo "✓ All CI checks passed!"
Problem: reload_config field in CGI scripts ignored
Cause: Field missing from YAMLConfig.Server.CGIScripts
Fix: Added field to CGIScriptConfig struct
Lesson: Always check YAMLConfig has all fields with yaml tags
Problem: /up returned 401 (auth) or started index tenant unnecessarily
Cause:
YAMLConfig.Server missing HealthCheck fieldparseServerConfig() not copying health check configFix:
// types.go - Added to YAMLConfig.Server
HealthCheck HealthCheckConfig `yaml:"health_check"`
// parser.go - Added to parseServerConfig()
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
Test Coverage: 270 lines added
// ❌ BUG: Field exists in YAMLConfig but not copied
type YAMLConfig struct {
Server struct {
HealthCheck HealthCheckConfig `yaml:"health_check"`
}
}
func (p *ConfigParser) parseServerConfig() {
// ... other fields copied
// ❌ FORGOT: p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
}
// ✅ FIX: Add the copy statement
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
How to catch: Write parser tests that verify field is copied.
// ❌ BUG: No yaml tag, field won't unmarshal
type ServerConfig struct {
Listen string // Won't unmarshal from YAML
}
// ✅ FIX: Add yaml tag
type ServerConfig struct {
Listen string `yaml:"listen"`
}
// ❌ BUG: Auth check after tenant routing (can be bypassed!)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleWebAppProxy(w, r) // Routes first
if !h.auth.CheckAuth(r) { // Auth never runs!
return
}
}
// ✅ FIX: Correct order
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 1. Health checks (before everything)
if h.config.Server.HealthCheck.Path != "" { /* ... */ }
// 2. Authentication (EARLY)
isPublic := auth.ShouldExcludeFromAuth(r.URL.Path, h.config)
needsAuth := h.auth.IsEnabled() && !isPublic
if needsAuth && !h.auth.CheckAuth(r) {
h.auth.RequireAuth(recorder)
return
}
// 3. Then routing
h.handleWebAppProxy(w, r)
}
How to catch: Write integration tests that verify security (auth bypass scenarios).
cd navigator
# Fetch latest from remote
git fetch origin
# Rebase on main (creates a clean linear history)
git rebase origin/main
# If conflicts, resolve them and continue
# git rebase --continue
# Make changes, run tests
go test ./...
golangci-lint run
git add -A
git commit -m "Fix: health check parsing"
cd .. # Back to showcase root
# Test Navigator fix with showcase deployment
# Push Navigator changes
cd navigator
git push origin HEAD:refs/heads/main
# Update submodule reference in showcase
cd ..
git add navigator
git commit -m "Update navigator: fix health check parsing"
git push
Note: If submodule is in detached HEAD state (common after git submodule update), the rebase step ensures you're building on the latest main branch before making changes.
# Build Navigator
cd navigator
go build -o bin/navigator cmd/navigator
# Or use make
make build
# Run with config
./bin/navigator config/navigator.yml
# Reload config (SIGHUP)
kill -HUP $(cat /tmp/navigator.pid)
# Debug config loading
LOG_LEVEL=debug ./bin/navigator config/navigator.yml
You need Navigator changes when:
Pattern: Fix Navigator first, test in showcase, then continue deployment plan.
navigator/CLAUDE.md - Comprehensive development guide (read first!)internal/config/types.go - All config structures (YAMLConfig + Config)internal/config/parser.go - YAML → Config conversioninternal/server/handler.go - Main HTTP request routingnavigator/CLAUDE.md for comprehensive guidenavigator/docs/ for user documentationLOG_LEVEL=debug to see what's happeningSearch for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer