{"record":{"id":"68fd30b69f895bc9","repo":"HMCL-dev/HMCL","slug":"storage-does-not-have-loginname","errorCode":null,"errorMessage":"storage does not have loginName","messagePattern":"storage does not have loginName","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java","lineNumber":88,"sourceCode":"        String apiRoot = JsonUtils.getString(metadata, \"serverBaseURL\");\n        if (apiRoot == null) {\n            throw new IllegalArgumentException(\"storage does not have API root.\");\n        }\n        AuthlibInjectorServer server = serverLookup.apply(apiRoot);\n        return fromStorage(metadata, privateData, downloader, server);\n    }\n\n    static AuthlibInjectorAccount fromStorage(\n            JsonObject metadata,\n            JsonObject privateData,\n            AuthlibInjectorArtifactProvider downloader,\n            AuthlibInjectorServer server) {\n        AccountID accountID = Account.readAccountID(metadata);\n        YggdrasilSession session = YggdrasilSession.fromStorage(metadata, privateData);\n\n        String loginName = JsonUtils.getString(metadata, \"loginName\");\n        if (loginName == null) {\n            throw new IllegalArgumentException(\"storage does not have loginName\");\n        }\n\n        if (privateData.get(\"profileProperties\") instanceof JsonObject profilePropertiesObject) {\n            Map<String, String> properties = JsonUtils.GSON.fromJson(\n                    profilePropertiesObject,\n                    JsonUtils.mapTypeOf(String.class, String.class));\n            GameProfile selected = session.getSelectedProfile();\n            ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository =\n                    server.getYggdrasilService().getProfileRepository();\n            profileRepository.put(selected.getId(), new CompleteGameProfile(selected, properties));\n            profileRepository.invalidate(selected.getId());\n        }\n\n        return new AuthlibInjectorAccount(accountID, server, downloader, loginName, session);\n    }\n}\n","sourceCodeStart":70,"sourceCodeEnd":105,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java#L70-L105","documentation":"The full fromStorage overload reads the \"loginName\" key from metadata after reconstructing the account ID and session; a null value throws IllegalArgumentException. The login name is required to rehydrate the account, so its absence means the persisted record is incomplete.","triggerScenarios":"Calling the AuthlibInjectorAccountFactory.fromStorage overload with metadata missing the \"loginName\" property.","commonSituations":"Storage entries written by older HMCL versions before loginName was persisted; manually trimmed accounts.json; schema migration bugs.","solutions":["Persist \"loginName\" in the account metadata when saving","Re-login the account to regenerate complete storage","During migration, map the legacy username field to loginName"],"exampleFix":"// before\nJsonObject meta = new JsonObject(); // no loginName\nfactory.fromStorage(meta, privateData, downloader, server);\n// after\nmeta.addProperty(\"loginName\", storedUsername);\nfactory.fromStorage(meta, privateData, downloader, server);","handlingStrategy":"validation","validationCode":"if (!metadata.has(\"loginName\")) {\n    throw new IllegalArgumentException(\"Persisted account missing loginName; re-login required\");\n}\nAuthlibInjectorAccount account = factory.fromStorage(metadata, privateData, downloader, server);","typeGuard":null,"tryCatchPattern":"try {\n    return factory.fromStorage(metadata, privateData, downloader, server);\n} catch (IllegalArgumentException e) {\n    log.warn(\"Account storage incomplete: {}\", e.getMessage());\n    return null;\n}","preventionTips":["Serialize loginName in every account save","Check for all required keys (accountID, loginName, serverBaseURL) before deserializing","Drop/re-login legacy storage entries lacking loginName"],"tags":["minecraft","authlib-injector","persistence","json"],"backgroundTag":"missing-required-argument","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}