Skip to content

fix: refuse outbound edits that strand route.final, and stop the Node Rules crash - #16

Merged
SealinGp merged 1 commit into
mainfrom
fix/dangling-route-final-node-rules-crash
Oct 7, 2026
Merged

SealinGp merged 1 commit into
mainfrom
fix/dangling-route-final-node-rules-crash

Conversation

@SealinGp

@SealinGp SealinGp commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Fixes #14.

Summary

The issue reports two symptoms with one trigger: a node filter that ends up matching no nodes.

1. sing-box refuses to start (default outbound not found: Other Nodes)

An empty filter is dropped from config.json (sing-box rejects a group with no members), but route.final could still name it. sing-box check parses the config without building the router, so it does not validate that reference: the config was saved, and the core only failed at start. On a host whose init system respawns, that is a crash loop.

UpdateOutboundsConfig now refuses any edit that would remove an outbound still named by:

  • route.final
  • a route rule (including sub-rules of logical rules)
  • a rule-set download_detour
  • a DNS server detour

The working config is left untouched and the error names the outbound and where it is used, for example "Other Nodes" (used by route.final).

Every outbound write goes through that one function, so this covers a node-rules apply, a subscription refresh and deleting an outbound by hand.

Only references the edit itself breaks are reported. A reference that was already dangling does not block later edits, so someone already in the broken state can still repair it from the panel.

2. Node Rules page crash (members is not iterable)

A filter with no members was sent as members: null (a nil Go slice), and the page iterates that field on every render.

  • The preview now always sends arrays for members and unmatched.
  • Filters send excludes: [] instead of null (the odd field noticed on the fallback filter).
  • The frontend normalises the preview where it enters the app, so the page also renders against a backend that has not been upgraded.

Behaviour change to be aware of

A subscription refresh that would empty a filter route.final points at now fails with this error until the route is repointed. The previous config keeps running. Emitting an empty group was not an option (sing-box rejects it), and silently repointing route.final would change where traffic exits without the operator choosing it.

Not addressed

The "Related" item at the end of the issue (the download_detour select not rendering in the Rule Sets dialog) was not investigated here.

Test plan

  • New Go tests reproduce the issue's scenario: dropping Other Nodes while route.final names it is refused and the config file is byte-identical afterwards
  • New Go tests: unreferenced outbounds can still be removed; an already-dangling reference does not block an unrelated edit; unreadable sections are skipped
  • New frontend test with the issue's members: null payload
  • go test for singbox/config/..., routes, architecture; bun test; bun run build
  • Reproduce in a browser on a host with node rules: all nodes matched by explicit filters, route.final on the fallback group, then Apply
  • Subscription refresh in that same state shows the new error

… Rules crash

Two bugs with one trigger: a node filter that matches no nodes (#14).

An empty filter is dropped from config.json, but route.final could still
name it. sing-box check does not validate that reference, so the config
was saved and the core then failed at start with "default outbound not
found". UpdateOutboundsConfig now refuses any edit that would remove an
outbound still named by route.final, a route rule, a rule-set
download_detour or a DNS server detour, and leaves the working config in
place. Only references the edit itself breaks are reported, so an
already-dangling one cannot block the edit that fixes it.

The Node Rules page threw "members is not iterable" because a filter
with no members was sent as members: null. The preview now always sends
arrays, filters send excludes: [] instead of null, and the frontend
normalises the preview where it enters the app.

Refs #14
@SealinGp
SealinGp merged commit a61f80d into main Oct 7, 2026
1 of 2 checks passed
@SealinGp
SealinGp deleted the fix/dangling-route-final-node-rules-crash branch October 7, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dangling route.final when the fallback node filter (Other Nodes) ends up empty + Node Rules page crashes (members is not iterable)

1 participant