27 medium platform portability
Part V: Environment & State | Challenge §27
27. Platform & Shell Portability
Severity: Medium | Frequency: Common | Detectability: Easy | Token Spend: Medium | Time: Medium | Context: Low
The Problem
Agent environments vary: macOS, Linux, Docker containers, CI runners. Shell assumptions and platform-specific behavior cause silent failures on non-target platforms.
macOS vs Linux differences:
$ sed -i 's/foo/bar/' file.txt
# Linux: works
# macOS: Error: invalid command code 's'
# macOS requires: sed -i '' 's/foo/bar/' file.txt
Shell-specific syntax:
$ tool --args "key=value key2=value2"
# Works in bash
# Fails in sh (no word splitting override)
# Behaves differently in zsh
Path assumptions:
#!/usr/bin/env python3
# Assumes python3 in PATH — not true in all containers
GNU vs BSD tool flags:
date --iso-8601 # GNU date
date -u +%Y-%m-%dT%H:%M:%SZ # portable
Impact
- Commands that work on the agent developer's machine fail silently in CI or container environments
- Shell syntax differences cause arg parsing to break without clear error
- Platform-specific path or tool assumptions prevent the tool from running at all
- Agent receives an exit 127 or shell error, not a structured JSON failure
Solutions
Portable shebang and runtime detection:
#!/usr/bin/env -S python3 -u
# -S: allows arguments after env command (GNU env >=8.30 / macOS 12+)
Explicit shell and version requirements:
{
"requires": {
"shell": "bash>=4.0",
"platform": ["linux", "darwin"],
"tools": ["curl>=7.0", "jq>=1.6"]
}
}
For framework design:
- tool doctor checks platform compatibility
- Framework abstracts platform differences (dates, paths, colors)
- All paths use forward slashes, never backslash (for cross-platform scripts)
Evaluation
| Score | Condition |
|---|---|
| 0 | Tool uses platform-specific flags or assumptions; fails on macOS/Linux without explanation |
| 1 | Tool works on target platform; fails on others with a raw shell or OS error, not a structured JSON error |
| 2 | tool doctor checks runtime and OS compatibility; failure emits a structured JSON error with platform context |
| 3 | Explicit platform/shell requirements declared in --show-requirements; framework abstracts all platform differences; tool doctor checks all dependencies |
Check: Run tool doctor --output json — verify it emits structured pass/fail checks for OS, shell, and required tools.
Agent Workaround
Always run tool doctor before the first command; inspect platform context in errors:
import subprocess, json, sys
def check_platform(tool: str) -> list[dict]:
result = subprocess.run(
[tool, "doctor", "--output", "json"],
capture_output=True, text=True,
)
try:
data = json.loads(result.stdout)
return [c for c in data.get("checks", []) if not c.get("ok")]
except json.JSONDecodeError:
return [] # tool doesn't support --doctor
failing = check_platform("tool")
if failing:
for check in failing:
print(f"Prereq failed: {check['name']} — {check.get('fix', 'no fix provided')}")
sys.exit(1)
Pass --output json and use explicit paths to avoid shell expansion differences:
# Avoid shell=True — shell syntax differs across platforms
result = subprocess.run(
["tool", "build", "--cwd", "/absolute/path/to/project", "--output", "json"],
capture_output=True, text=True, # not shell=True
)
Limitation: If the tool uses platform-specific binaries or shell syntax internally and provides no tool doctor command, the only signal is a non-zero exit code with stderr text — parse stderr for version or command-not-found patterns to identify the missing dependency