Debug Environment Variables
IO_CONTRACT
- input:
missing_var: str— 在非交互 shell /subprocess.run(shell=True)中丢失的环境变量名 - input:
shell_context: enum— 故障层(interactive /bash -c/ Python subprocess),用于边界隔离诊断 - output:
diagnosis: str— 变量消失点定位(.bashrc守卫 / 引号与 subprocess 边界 / 从未设置) - output:
fix: config— 按可靠性排序的修复:export 前移守卫之前 /BASH_ENV/.api_key文件回退 //etc/environment
原则 (Principles)
- 界生惑:壳有界而境无边,子不知父之所有;变量之失,多在层界交接之处,先明其界再究其源。
- 逐层而验:从父进程、
bash -c至 Python 子层逐层验证变量所在,定位消失点,不凭臆断。 - 修复有序:export 前移 →
BASH_ENV→ 文件回退(.api_key)→/etc/environment,依可靠性为序,勿倒置。 - 自动化不倚
.bashrc:CI、cron、子进程之环境变量,不倚.bashrc之 export,须用/etc/environment或文件回退;秘钥更不置.bashrc。
Genes (策略基因)
紧凑策略表示。条件→策略。需要深度时参考完整文档。
- [DEBU-001] 变量在交互 shell 存在但在
bash -c或子进程中丢失 → 优先检查.bashrc中的case $- in *i*)守卫,确认 export 是否位于守卫之前 - [DEBU-002] 需要在非交互 shell 中加载环境变量 → 设置
BASH_ENV指向配置文件,或将 export 语句移至.bashrc守卫块之前 - [DEBU-003] 自动化场景(CI/cron/subprocess)中环境变量不可靠 → 禁止依赖
.bashrc,改用/etc/environment或应用层文件回退机制 - [DEBU-004] 敏感凭证(API Key)在子进程中读取为空 → 实现文件回退策略(如读取
.api_key文件),避免将密钥硬编码在 shell 配置中 - [DEBU-005] 嵌套
subprocess.run(shell=True)导致引号解析错误 → 使用&&链接命令或 heredoc 结构,避免在双引号内嵌套单引号导致变量被外层 shell 消费 - [DEBU-006] 诊断变量消失点时缺乏定位依据 → 执行逐层验证(父进程 →
bash -c→ Python 子层),通过os.environ和echo对比定位具体失效层级
原理层·文言
环境之变,变量之惑。 壳有界而境无边,子不知父之所有。 知其源则知其变,明其界则明其惑。
DevOps — environment configuration, subprocess shells, credential injection.
When to Use
- Environment variables work in interactive shell but not in scripts
subprocess.run(shell=True)seems to lose environment variables- API keys, tokens, or credentials are empty at runtime despite being set
.bashrc/.profile/.envfiles seem to be ignored by automated processes
The Non-Interactive Shell Trap
Bash's .bashrc contains this guard by default:
case $- in
*i*) ;;
*) return;;
esac
Non-interactive shells (e.g., bash -c "...", subprocess.run(shell=True)) do NOT have the i flag, so they hit return before reaching any code after the guard. Exports placed after esac are invisible to non-interactive shells.
Process Substitution and Quoting
When nesting bash -c with Python inside subprocess.run(shell=True):
- Single quotes inside double-quoted
-cargs get consumed by the outer shell bash -c "export X=hello; python3 -c 'print(os...)'— the inner single quotes are eaten- Use
&&chaining or heredocs to avoid quoting issues
.bashrc Is Not Loaded Everywhere
- Non-interactive, non-login shells: never source
.bashrc - Login shells: source
.profile→ which may source.bashrc bash --login: sources.profile(not.bashrcdirectly)bash -i: sources.bashrc(interactive)bash -c: does NOT source anything unlessBASH_ENVis set
Debugging Steps
Isolate the boundary: Does the variable exist in the parent process?
import os; print(os.environ.get("VAR", "NOTSET"))Test at each layer:
- Interactive shell:
echo $VAR - Non-interactive shell:
bash -c "echo $VAR" - After sourcing:
bash -c "source ~/.bashrc; echo $VAR" - In Python:
bash -c "python3 -c 'import os; print(os.environ.get(\"VAR\"))'"
- Interactive shell:
Trace through the chain: Check where the variable disappears:
- If it exists interactively but not in
bash -c: the.bashrcguard is the culprit - If it exists in
bash -cbut not in Python: quoting/subprocess boundary issue - If it never exists: the variable was never set correctly
- If it exists interactively but not in
Fixes (in order of reliability)
Fix 1: Move Exports Before the Guard
Place export VAR=... before the case $- in ... esac block in .bashrc. Non-interactive shells will hit the export before hitting return.
Fix 2: Set BASH_ENV
export BASH_ENV=~/.bashrc
Non-interactive shells read the file pointed to by BASH_ENV.
Fix 3: File-Based Fallback (Most Reliable for Automated Systems)
Store credentials in a local file and have the application read it as fallback:
# In config.py or equivalent
import pathlib
_key_file = pathlib.Path(__file__).parent / '.api_key'
if _key_file.is_file():
self.api_key = _key_file.read_text().strip()
Place the file in a tracked but gitignored location (e.g., .api_key in .gitignore).
Fix 4: Use /etc/environment (Root Required)
All PAM-authenticated sessions read /etc/environment. This is the most reliable but requires root/sudo.
Verification Checklist
- Variable exists in interactive shell (
echo $VAR) - Variable survives
bash -c(non-interactive) - Variable is accessible in Python subprocess
- The application can read the variable (directly or via fallback)
Hermes Agent / Python Subprocess Specific Pitfalls
The subprocess.run(shell=True) Trap in Hermes
When Hermes Agent runs execute_code with shell=True, it invokes bash -c. This shell:
- Does NOT have the
iflag (non-interactive) - Does NOT source
.bashrcby default (even withBASH_ENVin some configurations) - Exports inside
bash -c "..."do NOT always propagate to child processes due to quoting/subprocess isolation
Symptoms: os.environ.get('API_KEY') returns empty even after source ~/.bashrc succeeds.
Why: The Python process inside bash -c runs in a child scope that may not inherit the exported variables due to how this environment spawns subprocesses. The echo inside bash works, but python3 cannot see them.
Reliable workaround: Don't rely on bash -c to propagate env vars to Python. Instead, use one of these:
- File-based fallback (best): Store API keys in a local file (e.g.,
.api_key) and have Python read it as fallback whenos.environis empty. - Hardcode at call site: Pass the key as a parameter when calling the subprocess.
- Never put secrets in
.bashrcfor automated systems — use a dedicated secrets manager or file.
.bashrc Exports Are Not Reliable for Subprocesses
Even when .bashrc exports are placed before the case $- in ... esac guard, non-interactive bash (bash -c) may still not source .bashrc at all (it depends on how bash was invoked). In Hermes Agent's environment, bash -c does NOT source .bashrc by default.
Rule of thumb: Never rely on .bashrc exports for any automated process (CI, cron, subprocess, daemon). Use /etc/environment or a file-based fallback instead.
Session Details
- Ubuntu .bashrc non-interactive trap — full case study from 2026-05-08 session with Semantic Scholar API key (Hermes Agent subprocess isolation discovered)
- API Key Fallback Pattern — file-based fallback for API keys in subprocess contexts (most reliable for automated systems)
- SS Dual-Key Failover — dual-key ring + automatic failover for Semantic Scholar API (iYTNXX legacy + s2k- prefix, key rotation on failure)
验证清单 · VERIFICATION
- 缺失变量已定位消失点(.bashrc 守卫/引号边界/从未设置)
- 修复方案按可靠性排序给出(export 前移/BASH_ENV/.api_key 文件回退)
- 已区分非交互 shell 与 subprocess 边界导致的变量丢失
- 修复后变量在目标 shell 可用
- 输出含 diagnosis 与 fix 两段,无凭记忆断言
约束规则 · RULES
- 输入约束: 参数类型、范围、格式必须校验
- 输出约束: 返回值结构、编码、命名必须一致
- 异常约束: 错误信息必须包含上下文和恢复建议
- 安全约束: 不执行未验证的任意代码,不暴露内部状态
Golden 集合 · GOLDEN SET
- Golden Input: 标准输入样本(覆盖正常路径)
- Golden Output: 预期输出(精确匹配或格式校验)
- Golden Error: 预期错误信息(覆盖失败路径)
Golden 集合是测试的单一真理来源。所有改进必须通过 golden 测试。
违反规则的操作视为不安全,必须拒绝或隔离。
每项验证必须可执行、可记录、可复现。验证失败时记录原因和修复。
Debug Env Variables---
(P032 去重: 以下为合并前第二份中的 1 行独有内容, 保留以防丢失)