fix(harness): stage all six harnesses on messages.yml via shared resolve/install scripts - #789
Merged
Merged
Conversation
messages.yml staged only the claude and grok CLIs, so a repo configured for codex/pi/vibe/kimi had every inbound message answered on claude. It warned rather than swapping silently, but it was still not the harness the operator chose - and on Anthropic credentials a single-provider fork may not even have. The cause was structural, not a missing case. Both the harness/provider/model resolution (~100 lines) and the install recipes (~150 lines) lived inside aeon.yml steps, so no other workflow could reach them. messages.yml carried a second, weaker copy that knew claude and grok - the same shape as the two-path grok trap in #784. Extracted: * scripts/resolve-harness.sh - decides HARNESS / AUTH_MODE / HARNESS_MODEL / MODEL_ARG, prints them as KEY=VALUE lines on stdout (summary on stderr). Takes an OPTIONAL skill name: pass it for per-skill harness:/model: overrides, omit it for surfaces that are not a per-skill run. * scripts/install-harness.sh - stages the CLI + provider auth for that harness. claude is a no-op so callers can invoke it unconditionally. Both workflows now call both scripts. Each keeps its own env: block, since secrets.* only resolves inside a workflow, but the logic - the half that drifted - is shared. Two latent bugs became reachable during the extraction and are fixed: * A missing provider credential died MID-HEREDOC under set -u. bash had already created the file, so the harness was left with a 0-byte config.toml and an error naming a shell variable instead of the secret. It never fired inside a workflow, where the env: block always binds the name; it appears the moment the script is callable standalone. Now fails closed and names the missing secret. * messages.yml gated the Anthropic gateway on `!= grok`, correct only while claude and grok were the sole options. With the other four reachable it would point their env at a gateway they never call. Now `= claude`. Tests - this logic had never been tested, because inside a workflow step the only way to exercise it was to dispatch a live run: * scripts/tests/test_resolve_harness.sh (28 cases) - precedence, AUTH_MODE detection, per-harness MODEL_ARG shape, the stdout contract. * scripts/tests/test_install_harness.sh (21 cases) - stubs npm/pipx and asserts the generated codex/kimi/vibe config plus the supply-chain pins. Equivalence verified by differential test against the inline versions: 832 resolution cases and every config-generating install case produce byte-identical output. actionlint clean, shellcheck clean, okf-validate clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last surface where "aeon supports six harnesses" wasn't true.
messages.ymlstaged only the claude and grok CLIs, so a repo configured for codex/pi/vibe/kimi had every inbound message answered on claude. It warned rather than swapping silently (#784), but it was still not the harness the operator chose — and on Anthropic credentials a single-provider fork may not even have.Why it wasn't just a missing case
Both the harness/provider/model resolution (~100 lines) and the install recipes (~150 lines) lived inside
aeon.ymlsteps, so no other workflow could reach them.messages.ymlcarried a second, weaker copy that knew claude and grok. Same shape as the two-path grok trap: a capability that exists on one surface and silently doesn't on another.Extracted to two scripts, both called by both workflows:
scripts/resolve-harness.sh— decidesHARNESS/AUTH_MODE/HARNESS_MODEL/MODEL_ARG, prints them asKEY=VALUElines (summary to stderr). Takes an optional skill name: pass it for per-skillharness:/model:overrides, omit it for surfaces that aren't a per-skill run. That's what lets one script serve both.scripts/install-harness.sh— stages that harness's CLI + provider auth.claudeis a no-op so callers can invoke it unconditionally.Each workflow still declares its own
env:block (secrets.*only resolves per workflow), but the logic — the half that drifted — is shared.Two latent bugs the extraction made reachable
A missing provider credential died mid-heredoc. Under
set -u,$OPENROUTER_API_KEYunbound killed the script aftercat >had created the file — leaving the harness with a 0-byteconfig.tomland an error naming a shell variable instead of the secret. It never fired inside a workflow, where theenv:block always binds the name; it appears the moment the script is callable standalone, which is exactly what this change does. Now fails closed and names the missing secret.The Anthropic gateway was gated on
!= grok. Correct while claude and grok were the only two options; with the other four reachable it would point their env at a gateway they never call. Now= claude, matchingaeon.yml.Equivalence
Refactors of live workflow logic are where silent behaviour changes hide, so I proved it rather than reasoned about it — replaying
main's inline blocks against the new scripts:KEY=VALUEoutput.$HOMEtrees.Tests
This logic had never been tested: inside a workflow step the only way to exercise it was to dispatch a live run.
test_resolve_harness.sh(28) — precedence,AUTH_MODEdetection, per-harnessMODEL_ARGshape, the stdout contract.test_install_harness.sh(21) — stubsnpm/pipx, asserts the generated codex/kimi/vibe config and the supply-chain pins.Two fixture traps worth recording, both of which would have produced tests that passed while testing nothing:
skill: { enabled: true, harness: "vibe" }). A block-style fixture is valid YAML but the per-skill greps match nothing, so every override case would silently test the fallback path.odd-onefixture pins vibe, which correctly forwards no--model— so the per-skill model is observable asHARNESS_MODEL, notMODEL_ARG. My first assertion had it backwards and failed against correct code.Verified live
Dispatched a real inbound message on a live instance configured for codex (run 30298390190):
The agent's own reply: "Running under the Aeon / Codex CLI harness." Job green, and zero
has no CLI stagedwarnings — before this change that same dispatch was answered on claude.actionlint,shellcheck,okf-validateclean; all shell suites pass.