netbirdio/netbird · error
trace failed: %v
Error message
trace failed: %v
What it means
Thrown by tracePacket (client/cmd/trace.go:113) after the daemon RPC proto.DaemonServiceClient.TracePacket returns an error. The CLI collapses it with status.Convert(err).Message(), which keeps only the gRPC message text and discards the status code - so an Unimplemented from an old daemon and a validation error from a running one look the same. The trace request is validated and executed by the daemon (source/dest IP parsing, firewall rule evaluation), not by the CLI.
Source
Thrown at client/cmd/trace.go:113
if err != nil {
return err
}
defer conn.Close()
client := proto.NewDaemonServiceClient(conn)
resp, err := client.TracePacket(cmd.Context(), &proto.TracePacketRequest{
SourceIp: args[1],
DestinationIp: args[2],
Protocol: protocol,
SourcePort: uint32(sport),
DestinationPort: uint32(dport),
Direction: direction,
TcpFlags: tcpFlags,
IcmpType: &icmpType,
IcmpCode: &icmpCode,
})
if err != nil {
return fmt.Errorf("trace failed: %v", status.Convert(err).Message())
}
printTrace(cmd, args[1], args[2], protocol, sport, dport, resp)
return nil
}
func printTrace(cmd *cobra.Command, src, dst, proto string, sport, dport uint16, resp *proto.TracePacketResponse) {
cmd.Printf("Packet trace %s:%d → %s:%d (%s)\n\n", src, sport, dst, dport, strings.ToUpper(proto))
for _, stage := range resp.Stages {
if stage.ForwardingDetails != nil {
cmd.Printf("%s: %s [%s]\n", stage.Name, stage.Message, *stage.ForwardingDetails)
} else {
cmd.Printf("%s: %s\n", stage.Name, stage.Message)
}
}
disposition := map[bool]string{View on GitHub (pinned to 93e97f4bf1)
Solutions
- Upgrade and restart the daemon to the same version as the CLI ('netbird service install' + restart) if the message mentions an unknown/unimplemented method
- Bring the client up first ('netbird up') and confirm with 'netbird status' before tracing
- Use plain host IPs (or the 'self' keyword shown in the command examples) for source and destination
- Re-run with the daemon log open - the daemon-side message usually carries the specific parse or evaluation failure
Example fix
// before
return fmt.Errorf("trace failed: %v", status.Convert(err).Message())
// after: keep the gRPC code so Unimplemented is distinguishable
if st, ok := status.FromError(err); ok && st.Code() == codes.Unimplemented {
return fmt.Errorf("trace failed: daemon does not support TracePacket, restart it with the current binary: %s", st.Message())
}
return fmt.Errorf("trace failed: %w", err) Defensive patterns
Strategy: try-catch
Validate before calling
// cheap preconditions before tracing
if net.ParseIP(src) == nil && src != "self" {
log.Fatalf("invalid source ip %q", src)
}
if net.ParseIP(dst) == nil && dst != "self" {
log.Fatalf("invalid destination ip %q", dst)
} Type guard
func isDaemonUnimplemented(err error) bool {
st, ok := gstatus.FromError(err)
return ok && st.Code() == codes.Unimplemented
} Try / catch
resp, err := client.TracePacket(ctx, req)
if err != nil {
if st, ok := gstatus.FromError(err); ok {
switch st.Code() {
case codes.Unimplemented:
return fmt.Errorf("daemon too old for trace: restart it with the current binary")
case codes.Unavailable:
return fmt.Errorf("daemon unreachable: %w", err)
}
}
return fmt.Errorf("trace failed: %w", err)
} Prevention
- Restart the service after upgrading the CLI so daemon and CLI RPCs match
- Bring the tunnel up ('netbird up') before tracing firewall paths
- Pre-validate IPs client-side; 'self' is the only non-IP keyword
When it happens
Trigger: Calling 'netbird debug trace' when: the daemon binary predates the TracePacket RPC (codes.Unimplemented); SourceIp/DestinationIp args are not valid IPs or the 'self' keyword; the firewall manager is not running because the client is down; or the context is canceled mid-trace.
Common situations: CLI upgraded but service not restarted (old daemon still serving); tracing while 'netbird status' shows disconnected; passing CIDR notation (192.168.1.0/24) where a host IP is required.
Related errors
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/d563dd5dcf7888ed.
Report an issue: GitHub.