{"record":{"id":"9bb706ce422fd966","repo":"NousResearch/hermes-agent","slug":"credential-source-bitwarden-but-the-access-token-e","errorCode":null,"errorMessage":"credential_source=bitwarden but the access-token env or project_id is empty.  Either set both, switch to credential_source: env, or set `proxy.allow_env_fallback: true` to opt into the legacy fallback behaviour.","messagePattern":"credential_source=bitwarden but the access-token env or project_id is empty\\.  Either set both, switch to credential_source: env, or set `proxy\\.allow_env_fallback: true` to opt into the legacy fallback behaviour\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/proxy_sources/iron_proxy.py","lineNumber":2220,"sourceCode":"                # bws warnings are non-secret status messages (e.g. \"no\n                # project found\", \"rate limited\"), but the taint analyzer\n                # can't tell that — log the count and let the operator\n                # rerun under verbose if they need detail.\n                if warnings:\n                    logger.warning(\n                        \"Bitwarden refresh produced %d warning(s); \"\n                        \"run `hermes secrets bitwarden status` for detail.\",\n                        len(warnings),\n                    )\n            else:\n                # NOTE: deliberately do not interpolate access_token_name\n                # in the log message — CodeQL's taint analyzer treats\n                # bitwarden_config values as secret-tainted (it can't\n                # distinguish the env-var NAME from the env-var VALUE).\n                # The name is non-secret but logging it just trips the\n                # check for no real benefit.\n                if not (bitwarden_config or {}).get(\"allow_env_fallback\"):\n                    raise RuntimeError(\n                        \"credential_source=bitwarden but the access-token \"\n                        \"env or project_id is empty.  Either set both, \"\n                        \"switch to credential_source: env, or set \"\n                        \"`proxy.allow_env_fallback: true` to opt into \"\n                        \"the legacy fallback behaviour.\"\n                    )\n                logger.warning(\n                    \"credential_source=bitwarden but access-token env or \"\n                    \"project_id is empty — proxy will fall back to parent env \"\n                    \"(allow_env_fallback=true).\",\n                )\n        except (ImportError,) as exc:\n            # The BWS module or one of its runtime deps isn't importable.\n            # Mirror the sibling branches: if allow_env_fallback isn't\n            # explicitly enabled, fail closed — credential_source=bitwarden\n            # with a unavailable module should not silently degrade to host\n            # env.  A wizard-time check can't catch a dependency that goes\n            # missing between setup and a later restart.","sourceCodeStart":2202,"sourceCodeEnd":2238,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/proxy_sources/iron_proxy.py#L2202-L2238","documentation":"credential_source=bitwarden is selected, but the Bitwarden access-token environment variable or the configured project_id (or both) is empty, so no refresh is even attempted. Consistent with the fail-closed policy, this raises unless proxy.allow_env_fallback is explicitly enabled (in which case a warning is logged and parent-env values are used). The log deliberately omits the token name to avoid tripping taint analysis.","triggerScenarios":"Proxy start with credential_source=bitwarden where the BWS access-token env var is unset/empty in the process env, or bitwarden_config's project_id is blank — e.g. after .env was recreated, the key was renamed, or setup never completed the BWS step.","commonSituations":"Fresh clone without the .env entries `hermes egress setup --bitwarden` would have written; CI runner missing the secret; the token env var name changed in config; the wizard was interrupted before writing project_id.","solutions":["Set both the BWS access-token env var (in ~/.hermes/.env — secrets only) and proxy bitwarden project_id in config.yaml; re-run `hermes secrets bitwarden setup` / `hermes egress setup` to populate them","If you no longer want BWS, switch to credential_source: env via `hermes egress setup --no-bitwarden`","To intentionally use host-env credentials, set proxy.allow_env_fallback: true in config.yaml"],"exampleFix":"# before: token env present but project_id empty\n# config.yaml:\nproxy:\n  credential_source: bitwarden\n  bitwarden:\n    project_id: \"\"        # <- empty\n# fix:\n    project_id: \"3f2a1b...\"\nhermes egress start\n\n# after: refresh branch executes instead of raising","handlingStrategy":"validation","validationCode":"import os, yaml\n\ndef bws_config_complete(cfg: dict) -> bool:\n    bw = (cfg.get('proxy') or {}).get('bitwarden') or {}\n    token = os.getenv(bw.get('access_token_env', 'BWS_ACCESS_TOKEN'), '')\n    return bool(token.strip()) and bool(str(bw.get('project_id', '')).strip())\n\n# gate start on bws_config_complete(load_config())","typeGuard":null,"tryCatchPattern":"try:\n    start_proxy(...)\nexcept RuntimeError as e:\n    if 'access-token env or project_id is empty' in str(e):\n        populate_bws_credentials()  # .env token + project_id in config.yaml\n        start_proxy(...)","preventionTips":["Run `hermes secrets bitwarden setup` end-to-end; it writes both halves of the pair","Never rename the access-token env var in config without updating .env","CI: assert the BWS token secret is present in the job before enabling egress"],"tags":["bitwarden","secrets","configuration","egress-proxy"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}