netbirdio/netbird · info
read of the policy table timed out
Error message
read of the policy table timed out
What it means
Debug endpoint error from handleClientStatus (proxy/internal/debug/handler.go:425): the requested accountID has no live embedded management client (nbembed.Client) in the provider's registry, so the debug UI/API answers 404 'Client not found: <accountID>'. The provider maps types.AccountID to per-account embedded clients spawned by the proxy.
Source
Thrown at client/internal/debug/nrpt_windows.go:94
done := make(chan result, 1)
go func() {
// the slot is released here rather than by the caller, so a read that
// outlives the timeout holds it until the provider answers
defer func() { <-nrptQueryInFlight }()
text, err := nrptPolicyTableText()
done <- result{text: text, err: err}
}()
select {
case res := <-done:
if res.err != nil {
return nil, res.err
}
return parseNRPTPolicyTable(res.text), nil
case <-time.After(nrptPolicyTimeout):
return nil, errors.New("read of the policy table timed out")
}
}
// nrptPolicyTableText calls the policy table method and returns the MOF text of
// its out parameters.
func nrptPolicyTableText() (text string, err error) {
// COM is per thread, and the collection is short lived, so the thread is
// pinned for the duration rather than initialized for the process.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
defer func() {
// The COM call chain is dynamically typed, so a provider that answers
// with an unexpected shape must not take the daemon down with it.
if r := recover(); r != nil {
err = fmt.Errorf("read NRPT policy table: %v", r)
}
}()View on GitHub (pinned to 93e97f4bf1)
Solutions
- List live accounts first via the clients overview (ListClientsForDebug-backed page or endpoint) and use an ID that appears there.
- If the account should be live, check the proxy logs for why its client is not running (management sync failure, removed domain).
- URL-encode the account ID exactly as shown by the list view.
Example fix
# before curl http://proxy-debug/debug/clients/<stale-or-typo-id>/status # -> 404 Client not found: <id> # after: enumerate live accounts, then query one of them curl http://proxy-debug/debug/clients | jq 'keys' curl http://proxy-debug/debug/clients/<id-from-list>/status
Defensive patterns
Strategy: validation
Validate before calling
// Call the list endpoint first and only query IDs it returns.
resp, err := http.Get(debugBase + "/clients")
if err != nil {
return err
}
var live map[string]any
if err := json.NewDecoder(resp.Body).Decode(&live); err != nil {
return err
}
if _, ok := live[accountID]; !ok {
return fmt.Errorf("account %s not live; pick from %d known", accountID, len(live))
} Prevention
- Always enumerate live clients before deep-linking to a per-account debug page.
- Re-check the list after proxy restarts or account changes.
- Treat 404 'Client not found' as 'not live (yet)', not as an error to page someone for.
When it happens
Trigger: GET /debug/clients/<accountID>/status (or the ?json variant) with an accountID that was never registered, whose client has not started yet, or whose client was removed (account deregistered or listener torn down); also a mistyped or URL-escaped ID.
Common situations: Operator copies an account ID from an older incident after the account disconnected; racing the debug UI against client startup; account removed from management so the proxy dropped its client.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- wireguard interface not initialized
- sync response persistence is disabled
- host argument required
- tun module not available
- jwt token is required
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/811664c4abc5af93.
Report an issue: GitHub.