{"record":{"id":"0b015333a6aebc84","repo":"hashicorp/nomad","slug":"unable-to-unmarshal-acltoken-w","errorCode":null,"errorMessage":"unable to unmarshal ACLToken: %w","messagePattern":"unable to unmarshal ACLToken: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/allocrunner/consul_hook.go","lineNumber":351,"sourceCode":"}\n\nfunc newResourcesBackend(allocID string, hr *cstructs.AllocHookResources, db cstate.StateDB) *resourcesBackend {\n\treturn &resourcesBackend{\n\t\tallocID:       allocID,\n\t\thookResources: hr,\n\t\tdb:            db,\n\t}\n}\n\nfunc decodeACLToken(b64ACLToken string, token *consulapi.ACLToken) error {\n\tdecodedBytes, err := base64.StdEncoding.DecodeString(b64ACLToken)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to process ACLToken: %w\", err)\n\t}\n\n\tif len(decodedBytes) != 0 {\n\t\tif err := json.Unmarshal(decodedBytes, token); err != nil {\n\t\t\treturn fmt.Errorf(\"unable to unmarshal ACLToken: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc encodeACLToken(token *consulapi.ACLToken) (string, error) {\n\tjsonBytes, err := json.Marshal(token)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to marshal ACL token: %w\", err)\n\t}\n\n\treturn base64.StdEncoding.EncodeToString(jsonBytes), nil\n}\n\n// This function will never return nil, even in case of error\nfunc (rs *resourcesBackend) loadAllocTokens() (map[string]map[string]*consulapi.ACLToken, error) {\n\tallocTokens := map[string]map[string]*consulapi.ACLToken{}","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/allocrunner/consul_hook.go#L333-L369","documentation":"decodeACLToken wraps a json.Unmarshal failure when decoding a base64-decoded Consul ACL token stored in the allocation's secure执行 directory. The token bytes exist but are not valid JSON matching consulapi.ACLToken, so the consul_hook cannot restore the alloc's Consul credentials. This is a defensive wrap so the underlying decode error (syntax, type mismatch, unexpected EOF) is preserved in the chain.","triggerScenarios":"loadAllocTokens reads the persisted token file from the alloc's token directory, base64-decodes it, and calls json.Unmarshal into *consulapi.ACLToken; unmarshal fails when the file is corrupt, truncated, hand-edited, or written by a different Nomad version with an incompatible schema.","commonSituations":"Disk corruption or partial writes on the client data dir; operator manually editing the token file; upgrading/downgrading Nomad so the stored token JSON no longer matches the struct; decoding a file that is base64 of something other than the marshaled token.","solutions":["Delete the stale/corrupt token file in the alloc dir and let Nomad re-fetch and re-persist the token from Consul on the next restart","Check the wrapped error (%w chain) to see the exact json.Unmarshal cause (e.g. invalid character, unexpected end of JSON input)","Ensure the token file was written by the same Nomad version; reschedule the allocation so it regenerates the file","Verify the base64 payload actually decodes to a consulapi.ACLToken JSON object, not a raw secret string"],"exampleFix":"// before: debugging with swallowed cause\nreturn fmt.Errorf(\"unable to unmarshal ACLToken: %w\", err)\n\n// after: log/inspect the wrapped cause and recover\nif err := json.Unmarshal(decodedBytes, token); err != nil {\n\tlogger.Warn(\"corrupt persisted ACL token, re-creating\", \"err\", err)\n\tos.Remove(tokenPath) // let the hook re-derive the token\n\treturn fmt.Errorf(\"unable to unmarshal ACLToken: %w\", err)\n}","handlingStrategy":"validation","validationCode":"decoded, err := base64.StdEncoding.DecodeString(raw)\nif err != nil { return err }\nif len(decoded) == 0 || !json.Valid(decoded) {\n\treturn fmt.Errorf(\"persisted ACL token is not valid JSON; removing stale file\")\n}\nvar probe map[string]any\nif err := json.Unmarshal(decoded, &probe); err != nil {\n\treturn fmt.Errorf(\"corrupt ACL token: %w\", err)\n}","typeGuard":"func isValidACLTokenJSON(b []byte) bool {\n\tvar t consulapi.ACLToken\n\treturn json.Unmarshal(b, &t) == nil && t.SecretID != \"\"\n}","tryCatchPattern":"if _, err := loadAllocTokens(...); err != nil {\n\tvar jsonErr *json.UnmarshalTypeError\n\tif errors.As(err, &jsonErr) || strings.Contains(err.Error(), \"unmarshal ACLToken\") {\n\t\t// recover: delete corrupt token file and re-derive token\n\t}\n}","preventionTips":["Never hand-edit files under the Nomad client data_dir","After Nomad version upgrades, reschedule allocs whose token files fail to load","Monitor client disks for corruption/truncation (fsck, SMART)","Treat wrapped json errors with errors.As to distinguish corruption from schema drift"],"tags":["consul","acls","json-unmarshal","persistence"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}