{"record":{"id":"b45be312bb600eb7","repo":"sipeed/picoclaw","slug":"get-device-id-via-whoami-w","errorCode":null,"errorMessage":"get device ID via whoami: %w","messagePattern":"get device ID via whoami: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/matrix/matrix.go","lineNumber":375,"sourceCode":"\t}\n\n\t// Wrap with dbutil for dialect support\n\twrappedDB, err := dbutil.NewWithDB(db, sqliteDriver)\n\tif err != nil {\n\t\t_ = db.Close()\n\t\treturn fmt.Errorf(\"wrap database: %w\", err)\n\t}\n\n\tcryptoHelper, err := cryptohelper.NewCryptoHelper(c.client, []byte(c.config.CryptoPassphrase), wrappedDB)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create crypto helper: %w\", err)\n\t}\n\n\tif c.client.DeviceID == \"\" {\n\t\tresp, whoamiErr := c.client.Whoami(ctx)\n\t\tif whoamiErr != nil {\n\t\t\t_ = db.Close()\n\t\t\treturn fmt.Errorf(\"get device ID via whoami: %w\", whoamiErr)\n\t\t}\n\t\tc.client.DeviceID = resp.DeviceID\n\t}\n\n\tif err = cryptoHelper.Init(ctx); err != nil {\n\t\tcryptoHelper.Close()\n\t\treturn fmt.Errorf(\"init crypto helper: %w\", err)\n\t}\n\n\tc.client.Crypto = cryptoHelper\n\tc.cryptoHelper = cryptoHelper\n\n\tlogger.InfoC(\"matrix\", \"Crypto helper initialized successfully\")\n\treturn nil\n}\n\nfunc markdownToHTML(md string) string {\n\textensions := (parser.CommonExtensions | parser.NoEmptyLineBeforeBlock) &^ parser.DefinitionLists","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/matrix/matrix.go#L357-L393","documentation":"Thrown by MatrixChannel.initCrypto when the logged-in client has no DeviceID and the homeserver lookup POST /_matrix/client/v3/account/whoami fails (pkg/channels/matrix/matrix.go:375). The crypto store is keyed by device ID, so it must be resolved before cryptoHelper.Init. The homeserver rejects whoami with 401 M_UNKNOWN_TOKEN for an invalid/expired access token; network failures, DNS errors, and a canceled startup context also surface here. The DB is closed on this path.","triggerScenarios":"Logging in with an access token that was revoked (password changed, sessions invalidated, token rotated by another bot instance); homeserver unreachable (wrong URL, DNS failure, firewall); reverse proxy returning 5xx on the client API; startup context canceled before device discovery; using a token whose device was deleted server-side.","commonSituations":"Long-lived bot deployments where the shared access token expired or was invalidated by the user; homeserver migration or restart during bot startup; typo in the homeserver URL; load balancer health-check gaps. DeviceID is typically empty when authenticating by token without a stored device_id from a previous run.","solutions":["Verify the token out-of-band: curl -H 'Authorization: Bearer <token>' https://homeserver/_matrix/client/v3/account/whoami — 401 means regenerate the token and update config","Check homeserver reachability and the configured server URL; fix DNS/proxy issues","Re-login to obtain a fresh token (and persist device_id to skip the whoami path next start)","Retry startup if it was a transient network failure"],"exampleFix":"# diagnose\n curl -s -H \"Authorization: Bearer $MATRIX_TOKEN\" \\\n   https://matrix.example.org/_matrix/client/v3/account/whoami\n # {\"errcode\":\"M_UNKNOWN_TOKEN\",\"error\":\"Invalid token\"} -> token expired: re-login and update config","handlingStrategy":"retry","validationCode":"// cheap token liveness check before starting the channel\nfunc tokenAlive(ctx context.Context, hs, token string) error {\n\treq, _ := http.NewRequestWithContext(ctx, http.MethodGet, hs+\"/_matrix/client/v3/account/whoami\", nil)\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil { return err }\n\tdefer resp.Body.Close()\n\tif resp.StatusCode == http.StatusUnauthorized {\n\t\treturn errors.New(\"access token invalid/expired: re-login required\")\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 5; attempt++ {\n\terr := matrixCh.Start(ctx)\n\tif err == nil { break }\n\tvar httpErr mautrix.HTTPError\n\tif errors.As(err, &httpErr) && httpErr.RespError != nil && httpErr.RespError.Errcode == \"M_UNKNOWN_TOKEN\" {\n\t\trefreshAccessToken() // permanent: rotate token, do not retry\n\t\tcontinue\n\t}\n\ttime.Sleep(backoff(attempt)) // transient network: backoff and retry\n}","preventionTips":["Persist device_id with the token so whoami is only needed on first boot","Monitor token expiry and rotate proactively for long-running bots","Distinguish 401 M_UNKNOWN_TOKEN (permanent) from network errors (transient) before retrying"],"tags":["go","matrix","mautrix","authentication","network","whoami","initialization"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}