feat(aterm): render --dry-run for a person, and split the exit codes (#1254) #1270

Merged
coilyco-ops merged 2 commits from aos/claude/mu55-dryrun-exits into main 2026-08-26 06:54:48 +00:00
Member

Closes #1254, all three parts including the opportunistic one.

Readable --dry-run

Angie [she] (Agentic Platform Engineer)
  seat         claude // frontier
  expression   acting
  workspace    agentic-os@aos/claude/mu55-dryrun-exits
  directory    /Users/kai/projects/coilyco-flight-deck/agentic-os
  shadow       leased
  personality  ⟐ tenacious  ◆ grounded
  brand        ▄▄ #9c8b31 accent   ▄▄ #1b1c18 background
  title        agentic-os@... // Agentic Platform Engineer // ⟐ ◆ Angie [she] // acting
  child        aos _native-shadow --harness claude --role platform --assigned-role -- agent-compose launch platform claude

The swatches are real background blocks and each personality renders in its own color, which is the first consumer of a field #1251 declared. Below 256 colors the swatch is dropped and only the hex prints: lipgloss degrades #9c8b31 to bright red there, which is worse than no swatch when the whole question is whether the brand looks right.

--dry-run --json

The raw plan is unchanged, just behind the flag #1253 declared. scripts/check-aos-release.sh asks for it, so the existing plan assertions stay exactly as they were.

Exit codes

code meaning
2 usage: unsafe slug, no role and no terminal, --json on a launch, bad working directory
3 off-roster role or seat, or a role with no launchable native seat
4 a dependency missing from PATH
5 the window failed to open
1 anything else

The code travels on the error, so fmt.Errorf("open the window: %w", err) keeps it and exitCodeFor reads it back through the wrapping. _session already exited 2 on a bad argument list and is unchanged.

The release check now asserts the codes rather than "non-zero":

if [ "$stale_status" -ne 3 ]; then ... fi
if [ "$missing_status" -ne 4 ]; then ... fi

The picker on /dev/tty

The issue called this opportunistic and asked for it only if the output routing was already open. It was.

Reproduced against the pre-change binary inside a pty, stdout redirected:

BEFORE  terminal: EXIT=1
        log:      aterm: a role is required when aterm is not attached to a terminal
AFTER   terminal: the role form renders
        log:      (empty, the plan goes here once a role is picked)

interactiveTTY now falls back to opening /dev/tty, and openConsole points the huh forms at it, so redirecting output no longer disables the picker. On a host with no controlling terminal both fail and the refusal is unchanged.

One thing worth flagging separately

docs/ is at 40 of 40 files and docs/aterm.md is now at 7,964 of 8,000 chars. Fitting this page cost trimming prose I added earlier in this batch, and #1255 through #1258 each want a walkthrough there is no slot for. That is a decision rather than a defect, so it is not fixed here.

Verification

  • just aterm-fmt, just aterm-lint, just aterm-test clean
  • pre-commit run clean on every touched file
  • new exit_test.go: every code above including the wrapping round trip, plus the human and machine --dry-run forms and the empty-field skip
  • pty repro above, before and after
Closes #1254, all three parts including the opportunistic one. ## Readable `--dry-run` ``` Angie [she] (Agentic Platform Engineer) seat claude // frontier expression acting workspace agentic-os@aos/claude/mu55-dryrun-exits directory /Users/kai/projects/coilyco-flight-deck/agentic-os shadow leased personality ⟐ tenacious ◆ grounded brand ▄▄ #9c8b31 accent ▄▄ #1b1c18 background title agentic-os@... // Agentic Platform Engineer // ⟐ ◆ Angie [she] // acting child aos _native-shadow --harness claude --role platform --assigned-role -- agent-compose launch platform claude ``` The swatches are real background blocks and each personality renders in its own `color`, which is the first consumer of a field #1251 declared. Below 256 colors the swatch is dropped and only the hex prints: lipgloss degrades `#9c8b31` to bright red there, which is worse than no swatch when the whole question is whether the brand looks right. ## `--dry-run --json` The raw plan is unchanged, just behind the flag #1253 declared. `scripts/check-aos-release.sh` asks for it, so the existing plan assertions stay exactly as they were. ## Exit codes | code | meaning | | --- | --- | | 2 | usage: unsafe slug, no role and no terminal, `--json` on a launch, bad working directory | | 3 | off-roster role or seat, or a role with no launchable native seat | | 4 | a dependency missing from `PATH` | | 5 | the window failed to open | | 1 | anything else | The code travels on the error, so `fmt.Errorf("open the window: %w", err)` keeps it and `exitCodeFor` reads it back through the wrapping. `_session` already exited 2 on a bad argument list and is unchanged. The release check now asserts the codes rather than "non-zero": ```sh if [ "$stale_status" -ne 3 ]; then ... fi if [ "$missing_status" -ne 4 ]; then ... fi ``` ## The picker on `/dev/tty` The issue called this opportunistic and asked for it only if the output routing was already open. It was. Reproduced against the pre-change binary inside a pty, stdout redirected: ``` BEFORE terminal: EXIT=1 log: aterm: a role is required when aterm is not attached to a terminal AFTER terminal: the role form renders log: (empty, the plan goes here once a role is picked) ``` `interactiveTTY` now falls back to opening `/dev/tty`, and `openConsole` points the huh forms at it, so redirecting output no longer disables the picker. On a host with no controlling terminal both fail and the refusal is unchanged. ## One thing worth flagging separately `docs/` is at 40 of 40 files and `docs/aterm.md` is now at 7,964 of 8,000 chars. Fitting this page cost trimming prose I added earlier in this batch, and #1255 through #1258 each want a walkthrough there is no slot for. That is a decision rather than a defect, so it is not fixed here. ## Verification * `just aterm-fmt`, `just aterm-lint`, `just aterm-test` clean * `pre-commit run` clean on every touched file * new `exit_test.go`: every code above including the wrapping round trip, plus the human and machine `--dry-run` forms and the empty-field skip * pty repro above, before and after
feat(aterm): render --dry-run for a person, and split the exit codes (#1254)
Some checks failed
ci / aos-eval-tests (pull_request) Successful in 6s
ci / ward-doctor (pull_request) Successful in 7s
ci / aos-cli-tests (pull_request) Successful in 27s
ci / gate (pull_request) Failing after 27s
3d95091f27
Three changes in one output surface.

`--dry-run` renders the identity, workspace, brand swatches, each personality
in its own color, and the child argv. The raw plan moves behind
`--dry-run --json`, and `scripts/check-aos-release.sh` asks for it. A swatch is
dropped below 256 colors, where #9c8b31 renders as bright red and lies about
the brand the operator is squinting at.

Failures split: 2 usage, 3 off-roster role or seat, 4 a dependency missing from
PATH, 5 the window failed to open, 1 anything else, and a child's own code
still passes through untouched. The code rides on the error through `%w`
wrapping, so a caller branches on it rather than grepping prose. The release
check now asserts 3 and 4 rather than "non-zero".

The picker runs on /dev/tty. `interactiveTTY` wanted stdin and stdout both to
be character devices, so `aterm > log` refused with "a role is required".
Reproduced in a pty against the pre-change binary and confirmed fixed.

Closes #1254

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
test: assert the release gate on exit codes rather than the refusal wording (#1254)
All checks were successful
ci / aos-eval-tests (pull_request) Successful in 7s
ci / ward-doctor (pull_request) Successful in 7s
ci / aos-cli-tests (pull_request) Successful in 27s
ci / gate (pull_request) Successful in 50s
f080c8047d
The release contract test pinned the exact string the stale-role check
printed, which #1254 replaced with an exit-code assertion. Point it at the
new contract, which is behavior rather than prose.

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os!1270
No description provided.