MHSanaei/3x-ui · error

remote AddClient: resolve tag %q: %w

Error message

remote AddClient: resolve tag %q: %w

What it means

Wraps the resolveRemoteID failure inside Remote.AddClient: the central panel tried to add a client to an inbound on a node, but could not map the inbound's tag to the node-local inbound ID first. The inner error is either the refresh failure (network/decode, errors 100-109) or the tag-not-found error (107).

Source

Thrown at internal/web/runtime/remote.go:545

// wireFingerprint hashes a wire payload so an unchanged inbound is cheap to detect.
func wireFingerprint(v url.Values) string {
	sum := sha256.Sum256([]byte(v.Encode()))
	return hex.EncodeToString(sum[:])
}

func (r *Remote) AddUser(ctx context.Context, ib *model.Inbound, _ map[string]any) error {
	return r.UpdateInbound(ctx, ib, ib)
}

func (r *Remote) RemoveUser(ctx context.Context, ib *model.Inbound, _ string) error {
	return r.UpdateInbound(ctx, ib, ib)
}

func (r *Remote) AddClient(ctx context.Context, ib *model.Inbound, client model.Client) error {
	id, err := r.resolveRemoteID(ctx, ib.Tag)
	if err != nil {
		return fmt.Errorf("remote AddClient: resolve tag %q: %w", ib.Tag, err)
	}
	payload := map[string]any{
		"client":     client,
		"inboundIds": []int{id},
	}
	if _, err := r.do(ctx, http.MethodPost, "panel/api/clients/add", payload); err != nil {
		return err
	}
	return nil
}

func (r *Remote) DeleteUser(ctx context.Context, ib *model.Inbound, email string) error {
	if email == "" {
		return nil
	}
	id, err := r.resolveRemoteID(ctx, ib.Tag)
	if err != nil {
		// Can't confirm the delete reached the node — surface it so the caller

View on GitHub (pinned to ad32144c42)

Solutions

  1. Resolve node reachability first if the inner error is a transport/decode failure (see the wrapped message).
  2. If the inner error is 'not found on node', push/re-save the inbound on the master so the node recreates it, then retry the client add.
  3. Avoid direct node-side inbound edits that break tag parity with the central panel.
  4. Run a reconcile for the node to converge inbound state before retrying per-client writes.

Example fix

// before: err: remote AddClient: resolve tag "n2-vmess-in": remote inbound with tag "n2-vmess-in" not found on node edge-2

// after: re-save the inbound on the central panel (re-pushes it to the node), then retry the client add
Defensive patterns

Strategy: retry

Validate before calling

if _, err := remote.ResolveTag(ctx, ib.Tag); err != nil {
    return fmt.Errorf("inbound not present on node; push it before adding clients: %w", err)
}

Type guard

func isResolveFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "resolve tag")
}

Try / catch

if err := remote.AddClient(ctx, ib, client); err != nil {
    if isResolveFailure(err) {
        if perr := pushInbound(ctx, ib); perr != nil { return errors.Join(err, perr) }
        return remote.AddClient(ctx, ib, client) // one converged retry
    }
    return err
}

Prevention

When it happens

Trigger: Adding a client to a node-assigned inbound whose tag does not exist on the node, or while the node is unreachable so the ID cache cannot be refreshed. The payload (client + inboundIds) is never sent.

Common situations: Client add raced a node-side inbound deletion; node offline mid-operation; tag drifted after out-of-band node edits.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/03c90ca537c58805. Report an issue: GitHub.