博客 Engineering

Stopping Health-Check Settings From Leaking Between Targets in Newt

英文文章. After a reconnect, Newt could give one target another target's expected status code. The merged fix decodes each payload into a fresh struct.

Gofosrl/newtnetworkingJSON decodinghealth checks

A subtle encoding/json behavior, diagnosed in public by the reporter, and fixed with a fresh struct per payload.

简短回答

When two Newt targets shared a backend and a websocket reconnect re-sent the configuration, one target could inherit the other's expected status code and be reported unhealthy forever. Newt decoded each registration payload into its existing state, and Go's encoding/json can reuse existing slice elements while decoding an array. The fix decodes into a fresh WgData and swaps it in only on success. It was merged as PR #462 and closed issue #411.

The problem

Newt is the tunneling client for the Pangolin project. Issue #411 described two targets on the same host:port, one expecting 200 and the other 401. After a reconnect, the first target logged unexpected status code: 200 (expected: 401) on every check. A restart cleared it until the next reconnect.

Root cause

The reporter traced it in the issue thread. handleConnect decoded the payload into the persistent n.wgData:

json.Unmarshal(jsonData, &n.wgData)

The relevant behavior is reuse of existing slice elements during decoding: a JSON null does not reset a non-pointer int. If targets arrived in a different order, or with a null status, old values survived into the wrong entries. The reporter linked a community pull request, #413, proposing the same approach. The supplied research records that PR as still open on Nov 3, 2026.

The fix

func (n *Newt) applyWgData(jsonData []byte) error {
    var wgData WgData
    if err := json.Unmarshal(jsonData, &wgData); err != nil {
        return err
    }
    n.wgData = wgData
    return nil
}

Three tests cover a reordered health-check payload with a null status, stale fields being cleared, and existing data being kept when the payload is malformed.

By the numbers

  • Repository: fosrl/newt, 921 stars (Nov 3, 2026), Go, AGPL-3.0
  • Files changed: 2 (1 source file, 1 test file)
  • Lines: +77 / -1
  • Commits: 1
  • Opened: Oct 6, 2026. Merged: Oct 12, 2026. Time to merge: 6 days, 2 hours
  • Review: approved and merged by a maintainer

How the review went

A maintainer approved, merged and replied "Thanks!"

常见问题解答

Why not reset fields individually?

Decoding into a fresh struct removes the whole class of positional-merge and null-handling problems at once, and the error path leaves the previous state untouched.

Where can I read more?

See the pull request, the original issue, and the earlier community PR #413.

目标

The hard part here was the diagnosis, and it happened in public, in the issue. The merged change is small because the cause was understood.

运行闭环

把生产信号转化为已审查的修复。

开始免费试用,了解 Prilog 如何把真实事故映射到代码级 Pull Request。