Doctrine

macOS shell snippets must be portable and bounded

fleet-opsshell-portabilitymacosoperator-ergonomicsevidence-quality

macOS shell snippets must be portable and bounded

The rule

Any shell snippet intended for a live fleet seat, feed post, handoff, or copy-paste diagnostic MUST assume macOS + zsh unless it explicitly executes a checked-in script with a bash shebang.

Two concrete consequences follow:

1. Do not rely on GNU timeout. macOS seats do not ship it by default. Use a tool-native timeout (curl --max-time, curl --connect-timeout, python3 subprocess.run(timeout=...), etc.) or a checked-in helper script. 2. Do not use status as a shell variable name in portable snippets. zsh reserves status as a read-only special parameter. Use names like http_code, exit_code, tool_state, or result_state.

Why

Fleet work often happens under time pressure on seats that default to zsh. A snippet that works in bash but fails on the first paste into zsh creates shell noise right when the operator is trying to gather evidence. That converts a simple live check into a context switch: the seat has to debug the tool instead of proving the underlying question.

The failure pattern is predictable:

  • A seat pastes a snippet with status=$(...) and zsh rejects it because
  • status is read-only.

  • A seat pastes a snippet that shells out to GNU timeout, but the binary is
  • not installed on macOS, so the diagnostic never runs.

  • The operator gets error text instead of the evidence table they were actually
  • trying to collect.

This doctrine treats shell portability as evidence quality, not just style.

Canonical guidance

For one-off network checks

  • Prefer curl --max-time <seconds> --connect-timeout <seconds> over wrapping
  • curl in an external timeout binary.

  • Prefer explicit variable names: http_code, json_body, row_count,
  • probe_state.

For repeated audit patterns

  • Do not keep re-typing ad hoc snippets in feed replies or handoff docs.
  • Promote the pattern into a checked-in helper script, then point future seats
  • at the helper.

  • Current example in this repo:
  • ./scripts/postgrest-count-compare.sh camber heartwood <table...>

For repo scripts

  • Standalone scripts with #!/bin/bash are allowed to be bash-specific, but
  • if their internals are likely to be copied into docs or terminal snippets, prefer portable naming there too.

Scope

In scope:

  • Feed posts containing runnable shell
  • Diagnostic packets and handoff docs
  • README / AGENTS command examples
  • One-liners pasted into Codex / Claude / Gemini seats
  • New fleet helper scripts

Out of scope:

  • Purely internal bash implementation details that are never surfaced as
  • copy-paste snippets

  • Non-shell programs using their own native timeout APIs

Sourcing

Sourced by a 2026-04-17 Codex fleet audit pass where a live table-count comparison had to be re-run after two environment wrinkles surfaced on-seat:

  • timeout was not installed on the macOS seat
  • zsh rejected status=... because status is read-only

The work itself was fine; the shell surface was noisy. This doctrine exists so future seats get evidence first and shell debugging second.