{"record":{"id":"69001dd96d22fe7d","repo":"gastownhall/beads","slug":"credential-command-returned-unparseable-json-w","errorCode":null,"errorMessage":"credential command returned unparseable JSON: %w","messagePattern":"credential command returned unparseable JSON: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/creds/command.go","lineNumber":153,"sourceCode":"\tcredCacheMu.Unlock()\n\treturn token, username, expiry, nil\n}\n\n// parseCredential extracts the token (and any username/expiry) from a helper's\n// stdout. A JSON object is read as the ExecCredential/getToken envelope; otherwise\n// the trimmed output is taken as a bare token. A bare value containing whitespace is\n// rejected — it is almost always an error message, and using it as a credential\n// would only fail confusingly downstream.\nfunc parseCredential(raw []byte) (token, username string, expiry time.Time, err error) {\n\ttrimmed := bytes.TrimSpace(raw)\n\tif len(trimmed) == 0 {\n\t\treturn \"\", \"\", time.Time{}, fmt.Errorf(\"credential command produced no output\")\n\t}\n\n\tif trimmed[0] == '{' {\n\t\tvar c execCredential\n\t\tif jerr := json.Unmarshal(trimmed, &c); jerr != nil {\n\t\t\treturn \"\", \"\", time.Time{}, fmt.Errorf(\"credential command returned unparseable JSON: %w\", jerr)\n\t\t}\n\t\ttoken = c.Token\n\t\tif token == \"\" {\n\t\t\ttoken = c.AccessToken\n\t\t}\n\t\tif token == \"\" {\n\t\t\treturn \"\", \"\", time.Time{}, fmt.Errorf(\"credential command JSON has no token/access_token field\")\n\t\t}\n\t\tswitch {\n\t\tcase c.ExpirationTimestamp != \"\":\n\t\t\tif t, perr := time.Parse(time.RFC3339, c.ExpirationTimestamp); perr == nil {\n\t\t\t\texpiry = t\n\t\t\t}\n\t\tcase c.ExpiresIn > 0:\n\t\t\texpiry = time.Now().Add(time.Duration(c.ExpiresIn) * time.Second)\n\t\t}\n\t\treturn token, c.Username, expiry, nil\n\t}","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/creds/command.go#L135-L171","documentation":"When the credential command's output starts with '{', parseCredential treats it as a JSON exec-credential envelope. If json.Unmarshal fails, the JSON is malformed and this error wraps the unmarshal failure. The command is supposed to emit either valid JSON or a bare token.","triggerScenarios":"The helper prints a partial/truncated JSON document, pretty-printed output with a trailing error appended, or invalid JSON such as '{token: abc}' without quoting.","commonSituations":"A custom credential script emitting hand-built JSON with unquoted values; helper mixing JSON output with log lines; proxy/firewall injecting an error page before the JSON.","solutions":["Read the wrapped %w JSON syntax error to find the malformed offset.","Run the credential command manually and inspect raw stdout for corruption or extra output.","Fix the helper to emit valid JSON: {\"token\":\"...\",\"expirationTimestamp\":\"...RFC3339...\"}.","Alternatively make the helper print just a bare token (no braces) so the JSON path is skipped."],"exampleFix":"// before\n{\"token: ghp_abc}   // invalid JSON\n// after\n{\"token\":\"ghp_abc\",\"expirationTimestamp\":\"2026-01-01T00:00:00Z\"}","handlingStrategy":"validation","validationCode":"var probe map[string]any\nif err := json.Unmarshal(raw, &probe); err != nil {\n    // output is not valid JSON; fix helper output first\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate JSON with encoding/json in the helper instead of string concatenation.","Ensure the helper writes nothing but the JSON envelope to stdout.","Avoid proxies/log wrappers that inject text into stdout."],"tags":["credentials","json","parsing"],"backgroundTag":"credential-json-unparseable","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}