From 49773c18de2ef0dc9c5901d28eec3c7e3bed780f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 1 Jul 2026 14:02:13 +0200 Subject: [PATCH] fix(xray): force full restart for inbounds with a VLESS reverse client Hot-applying an inbound change swaps it via DelInbound+AddInbound on the running core. That unregisters any client's reverse.tag handler on the xray-core side without closing the bridge's already-established connection, so the reverse tunnel is silently orphaned until someone manually restarts xray. diffInbounds now bails out of the hot-apply path whenever the old or new inbound carries a reverse-tagged client, falling back to a full restart, which actually drops the socket and lets the bridge redial on its own. Also scope the .claude ignore rule to its contents (.claude/*) instead of the whole directory, so individual files under .claude/ can be tracked selectively. --- .gitignore | 2 +- internal/xray/hot_diff.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0991d4f30..fd67a2625 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .idea/ .vscode/ .cursor/ -.claude/ +.claude/* .cache/ .sync* diff --git a/internal/xray/hot_diff.go b/internal/xray/hot_diff.go index 6a9816bc8..21a4ed616 100644 --- a/internal/xray/hot_diff.go +++ b/internal/xray/hot_diff.go @@ -108,6 +108,10 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool { if oldIb.Tag == apiTag || oldIb.Tag == "api" { return false } + if exists && (inboundHasReverseClient(oldIb) || inboundHasReverseClient(newIb)) { + logger.Debug("hot diff: inbound [", oldIb.Tag, "] carries a reverse-tagged client, forcing a full restart instead of a hot swap") + return false + } diff.RemovedInboundTags = append(diff.RemovedInboundTags, oldIb.Tag) if exists { raw, err := json.Marshal(newIb) @@ -134,6 +138,31 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool { return true } +func inboundHasReverseClient(ib *InboundConfig) bool { + if ib == nil { + return false + } + var settings struct { + Clients []struct { + Reverse json.RawMessage `json:"reverse"` + } `json:"clients"` + } + if err := json.Unmarshal(ib.Settings, &settings); err != nil { + return false + } + for _, c := range settings.Clients { + if len(c.Reverse) == 0 { + continue + } + var tag any + if err := json.Unmarshal(c.Reverse, &tag); err != nil || tag == nil { + continue + } + return true + } + return false +} + // diffOutbounds fills diff with outbound removals/additions keyed by tag. // The first outbound is xray's default handler and the API can only append, // so any change to its identity or content forces a restart. Reordering of