Skip to content

Device can become permanently stuck: activation reports failure while the registration persists, and unbind cannot recover it #107

Description

@u-ichi

Summary

During first-time setup the app can leave a device registered on the xiaozhi backend while reporting activation as failed. Once in this state the user cannot recover: every retry fails, and unbinding from the device's own menu also fails. Three separate issues combine to make this a dead end.

Observed behavior

  • StackChan World for Android v1.2.2 shows device activation and binding failed. please try again during setup.
  • Unbinding from the device's settings menu shows Invalid response from server.
  • The device itself shows no error at all. It joins the 2.4 GHz network, gets a DHCP lease, and performs TLS traffic.
  • xiaozhi.me and api.xiaozhi.me are reachable from the phone. No firewall rules, no country restrictions, no DNS filtering, no client isolation.

Issue 1 — "already added" is detected by exact-matching a localized message

app/lib/util/XiaoZhi_util.dart, agentsDevicesActivate():

} else if (response.statusCode == 400) {
  ///Already added
  XiaozhiResponse xiaozhiResponse = XiaozhiResponse.fromJsonT(response.data);
  if (xiaozhiResponse.message == "该设备已经添加过,请不要重复添加") {
    return true;
  }
}
return false;

If the backend returns this message in a different language, with different punctuation, or with reworded text, the comparison fails and an already-registered device is treated as an activation failure — permanently, with no way for the user to tell why.

Suggestion: match on an error code rather than the human-readable message.

Issue 2 — activation can succeed while the app reports failure, leaving a partial registration

app/lib/view/popup/select_blue_device.dart, queryConfiguration():

final checkDevice = await XiaoZhiUtil.shared.serialNumberGetDevice(serialNumber);
if (checkDevice == null || checkDevice.agent_id == null) {
  //activatefail
  return false;
}

This GET runs immediately after the activation POST. If agent_id is not yet populated — backend not yet consistent, or no agent assigned — the app aborts. But the device is already registered on xiaozhi at this point. No toast is shown on this path, so the user gets no indication of what happened, and bindDevice() is never reached, so the M5Stack side stays unbound while the xiaozhi side is bound.

Suggestion: retry with a short backoff before giving up, and show a distinct message on this path.

Issue 3 — unbind failure surfaces as "Invalid response from server" with the reason discarded

firmware/main/hal/hal_account.cpp, unbindAccount():

cJSON *code = cJSON_GetObjectItem(root, "code");
if (code && cJSON_IsNumber(code) && code->valueint == 0) {
    onLog("Account unbound successfully");
} else {
    cJSON *msg = cJSON_GetObjectItem(root, "message");
    if (msg && cJSON_IsString(msg)) {
        onLog(std::string("Unbind failed: ") + msg->valuestring);
    } else {
        onLog("Invalid response from server");   // <- what users actually see
    }
}

Reaching that last branch means: HTTP 200 was returned, the body parsed as JSON, code != 0, and no message field was present.

In server/internal/controller/device/device_v2_unbind_device.go, the only error paths that return without a message are the two immediately following the xiaozhi call:

unbindResponse, err := xiaozhi.UnbindDevice(&req.Mac)
if err != nil {
    return nil, gerror.NewCode(gcode.CodeInternalError)      // no message
}
if !unbindResponse {
    g.Log().Error(ctx, "xiaozhi Unbind Device failed:")
    return nil, gerror.NewCode(gcode.CodeInternalError)      // no message
}

Every other error path in that function carries a message. This suggests users seeing Invalid response from server are hitting a failing xiaozhi-side unbind — but the reason is discarded, so neither the user nor support can tell.

Suggestion: attach a message to these two error returns.

Why these combine into a dead end

Issue 2 creates the partial registration. Issue 1 makes every subsequent retry fail. Issue 3 makes the escape hatch fail and hides the reason. The only remaining path is emailing support to clear the record on the backend, which is what several users have ended up doing — see #105 and the community thread "Stackchan unboxing and setup troubleshoot", where reports of "invalid response from the server" and "device configuration feature not found" appear repeatedly with no resolution.

Environment

  • StackChan World for Android v1.2.2
  • Google Pixel 10 Pro Fold, Android 17
  • Router: UniFi Express 7. SSID is 2.4 GHz only, WPA2, PMF disabled, client isolation off, standard (non-hotspot) application, native network. No country restrictions or threat-management rules.
  • Device firmware: v1.2.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions