{"record":{"id":"36dc97b584cec28c","repo":"charmbracelet/crush","slug":"header-s-w","errorCode":null,"errorMessage":"header %s: %w","messagePattern":"header (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/config.go","lineNumber":543,"sourceCode":"// header\" is clearly \"absent when the var isn't set.\"\n//\n// See ResolvedEnv for guidance on picking a resolver.\nfunc (m MCPConfig) ResolvedHeaders(r VariableResolver) (map[string]string, error) {\n\tif len(m.Headers) == 0 {\n\t\treturn map[string]string{}, nil\n\t}\n\tout := make(map[string]string, len(m.Headers))\n\t// Sort keys so failures are reported deterministically when more\n\t// than one header would fail.\n\tkeys := make([]string, 0, len(m.Headers))\n\tfor k := range m.Headers {\n\t\tkeys = append(keys, k)\n\t}\n\tslices.Sort(keys)\n\tfor _, k := range keys {\n\t\tv, err := r.ResolveValue(m.Headers[k])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"header %s: %w\", k, err)\n\t\t}\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tout[k] = v\n\t}\n\treturn out, nil\n}\n\n// ResolvedArgs returns l.Args with every element expanded through the\n// given resolver. A fresh slice is allocated; l.Args is never mutated.\n// On the first resolution failure it returns nil and an error\n// identifying the offending positional index; the inner resolver error\n// is already sanitized by ResolveValue and is wrapped with %w so\n// errors.Is/As continues to work.\n//\n// Empty resolved values are kept (a deliberate \"empty positional arg\"\n// like --flag \"\" is sometimes valid), matching MCPConfig.ResolvedArgs.","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/config/config.go#L525-L561","documentation":"MCPConfig.ResolvedHeaders resolves each MCP server header value through VariableResolver.ResolveValue (iterating keys in sorted order), wrapping failures as `header <name>: <cause>`. Empty resolved values are skipped. This lets headers like Authorization source their values from env/secrets via {{env.TOKEN}} style references.","triggerScenarios":"Resolving an MCP config whose `headers` map contains a value with an unresolvable variable — e.g. \"Authorization\": \"Bearer {{env.MCP_TOKEN}}\" where MCP_TOKEN is unset or the reference is malformed.","commonSituations":"Auth headers templated from secrets present locally but absent in CI/production; rotating a secret and removing the env var; typo in the variable key inside the header value.","solutions":["Read the wrapped cause and header name to identify the failing entry.","Export the referenced environment variable before starting crush.","Move the secret into crush's secrets/env configuration so it resolves reliably.","Fix the {{...}} variable spelling in the header value in crushrc."],"exampleFix":"// before (crushrc)\nmcp acme --url 'https://acme/mcp' --header 'Authorization=Bearer {{env.ACME_TOKEN}}' // ACME_TOKEN unset\n// after\nexport ACME_TOKEN=...  # then rerun crush","handlingStrategy":"validation","validationCode":"# ensure env vars used in MCP headers are set before starting crush\ngrep -o '{{env\\.[A-Z_]*}}' crushrc | sed 's/{{env\\.//;s/}}//' | sort -u | while read -r v; do\n  [ -n \"${!v+x}\" ] || echo \"missing env var: $v\"\ndone","typeGuard":null,"tryCatchPattern":"headers, err := m.ResolvedHeaders(resolver)\nif err != nil {\n    return fmt.Errorf(\"cannot resolve MCP headers: %w\", err)\n}","preventionTips":["Export auth secrets (tokens) before launching crush in any environment.","Mirror required secrets into CI/CD environments explicitly.","Prefer empty-header skipping semantics: leave a header unset rather than templating an optional secret.","Review header templates after secret rotation."],"tags":["config","mcp","variable-resolution","headers","env"],"backgroundTag":"unresolved-config-variable","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}