{"record":{"id":"6f85447417f10307","repo":"gastownhall/beads","slug":"credential-command-produced-no-output","errorCode":null,"errorMessage":"credential command produced no output","messagePattern":"credential command produced no output","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/creds/command.go","lineNumber":147,"sourceCode":"\tif expiry.IsZero() {\n\t\texpiry = now.Add(credDefaultTTL)\n\t}\n\n\tcredCacheMu.Lock()\n\tcredCache[command] = cachedCred{token: token, username: username, expires: expiry}\n\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","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/creds/command.go#L129-L165","documentation":"parseCredential parses the raw bytes returned by the credential command. If the trimmed output is empty, there is no token to extract, so this error is thrown. It guards against helpers that succeed silently (exit 0) but print nothing.","triggerScenarios":"The credential command exits 0 but writes nothing to stdout — e.g. an unauthenticated helper that prints its message to stderr only, or a script with an empty success path.","commonSituations":"Credential helper logged out but returning success; a wrapper script swallowing stdout; wrong command configured that prints nothing for the given host.","solutions":["Run the credential command manually and confirm it prints a token or JSON envelope on stdout.","Re-authenticate the helper (e.g. `gh auth login`, `docker login`).","Check the configured credential command — a wrong or stub command may produce no output.","Verify the helper supports the requested host/repository and is not silently no-oping."],"exampleFix":"// before\n#!/bin/sh\n# helper prints nothing\necho -n \"\"\n// after\n#!/bin/sh\necho -n \"{\\\"token\\\":\\\"ghp_xxx\\\",\\\"expirationTimestamp\\\":\\\"2026-01-01T00:00:00Z\\\"}\"","handlingStrategy":"validation","validationCode":"out, err := exec.Command(helper, args...).Output()\nif err == nil && len(bytes.TrimSpace(out)) == 0 {\n    // helper produced no output; re-authenticate or fix helper first\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure the helper always writes a token to stdout on success.","Send helper diagnostics to stderr, never stdout.","Re-authenticate when logged out; verify with a manual run of the command."],"tags":["credentials","parsing","empty-output"],"backgroundTag":"credential-helper-empty-output","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}