Skip to content

Troubleshooting

wiki edited this page Sep 4, 2026 · 1 revision

Troubleshooting

My gate does not fire on a parameterized route

It should. If it does not, check the version: the gate used to be a global middleware that matched a route identifier built from the live URL (GET:/users/42) against an index keyed by the registered pattern (GET:/users/{id}). Those never match, so the gate silently did nothing for every route with a path parameter.

Current versions attach per route at build time, with no identifier and nothing to look up. Make sure you are not composing the deprecated DependencyGateMiddleware yourself.

The gate never refuses anything

In order of likelihood:

  1. The dependency name does not match. Dependencies() says "db", your check or ReportFailure says "database". The mismatched name is permanently StatusUnknown — and TreatUnknownAs defaults to StatusUp, so the gate serves. Use constants.
  2. The requirement is soft. NewSoftRequirement never refuses; it only puts state in the context.
  3. The gate is disabled. EnableDependencyGate — check it is still true in a config you built by hand.
  4. The route does not implement HealthDepRoute. With a pointer receiver on Dependencies(), the route must be registered as a pointer.

Everything 503s right after startup

Almost certainly TreatUnknownAs: StatusDown combined with checks that have not produced a result — either the synchronous startup pass failed to run them, or the dependency names in the requirements do not match any registered check.

Look at /status: a dependency listed as UNKNOWN after boot is one nothing reports on.

Startup is slow

The synchronous check pass runs every registered check once before the listeners bind. A check with a long timeout against a dependency that is down adds roughly its timeout to boot.

Give every check a WithTimeout you are willing to pay at startup, and consider CheckModePassive for anything expensive that no route hard-depends on.

A partial config turned things off

health.WithHealth(&health.Config{LivePath: "/healthz"}) // ← everything else is zero

A non-nil config is used verbatim. CheckInterval: 0 disables the ticker, SnapshotTTL: 0 disables caching, Router.Addr: "" is not a port. Use health.NewConfig(...).

I cannot register a check before Run

You cannot resolve the registry from the container before the extension's OnInitialize has run — it does not exist yet. Declare checks with WithCheck / WithChecks, or hold the extension value and call RegisterCheck from your own extension's OnInitialize.

/ready returns 200 while a dependency is degraded

By design. /ready returns 503 only when the overall status is DOWN; DEGRADED means "working, with problems" and keeps the replica in rotation.

If a degraded dependency should take the replica out, make the check report StatusDown instead — or gate at the route level with WithMinStatus(health.StatusUp).

A restart loop during a database outage

Check what your liveness probe points at. /live runs no checks precisely because wiring dependency checks into liveness restarts every replica during a dependency outage — turning a recoverable outage into a crash loop, with the replicas' reconnection storm on top.

Liveness → /live. Readiness → /ready.

The 503 does not say which dependency failed

Deliberate. Naming it maps your internal service topology for anyone probing endpoints during an outage. The identifier and its state go to the logger configured in MiddlewareConfig.Logger, which the extension sets by default.

If you see no log line either, something replaced the middleware config with one whose Logger is nil.

A circuit breaker is open and nothing shows it

Use NewCircuitBreakerWithStore rather than NewCircuitBreaker. The bare form keeps its state to itself, so nothing appears on /status and the gate cannot act on it.

Probes hammer my dependencies

They should not: /ready and /status are served from a snapshot with a 5s TTL, so a 1-second probe interval does not execute checks 60 times a minute.

If checks are running per request, SnapshotTTL is probably 0 — see the partial-config trap above.

/status is reachable from the internet

Fix that. It lists dependency identifiers, failure counts, latencies and circuit states — an outage map for anyone who asks. Bind the health router to loopback or a private interface:

health.WithHealthRouter(rx.RouterConfig{Addr: "127.0.0.1:9091"})