Skip to content

fix(agents): honour profile model for Claude and harden agent test harness - #522

Merged
8nevil8 merged 3 commits into
mainfrom
test/agent-env-hardening
Sep 1, 2026
Merged

fix(agents): honour profile model for Claude and harden agent test harness#522
8nevil8 merged 3 commits into
mainfrom
test/agent-env-hardening

Conversation

@8nevil8

@8nevil8 8nevil8 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

The model chosen in codemie setup never reached the wire for Claude. claude.plugin.ts maps the profile's model onto ANTHROPIC_MODEL only, but Claude Code ignores that variable — its precedence is --model > ~/.claude/settings.json "model" > its own default tier. So a developer with model pinned in ~/.claude/settings.json silently ran every session on that model, and one without it silently ran on the default (opus) tier.

This PR fixes that, and separately hardens two agent test-harness defects that made the suite fail locally regardless of product state.

Changes

fix(agents) — model selection

  • Inject --model $CODEMIE_MODEL in the claude enrichArgs hook, beside the existing --plugin-dir injection, with the same "already present" guard.
  • An explicit --model / --model= in argv still wins — CodeMie never overrides a user's own choice.
  • A blank CODEMIE_MODEL is treated as absent rather than forwarded as --model '': anthropic-subscription deliberately blanks it so the Claude CLI applies its own defaults.

test(tests) — harness hardening

  • getTestEnvValue / getCodemieTestUrl / getCodemieTestModel treat empty and whitespace-only env values as absent. CodeMie exports its whole CODEMIE_* block into any session it launches, so running the suite from such a shell inherited CODEMIE_MODEL='', and ?? let that empty string beat the intended default — every generated profile was written with no model.
  • CI_CODEMIE_URL now falls back to CODEMIE_URL, so no .env.test.local is needed inside a CodeMie-launched shell.
  • agent-assistant / agent-skills copy SSO credentials into the worker's temp CODEMIE_HOME. CREDENTIALS_DIR/FALLBACK_FILE in src/utils/security.ts are module-level constants resolved at import time, so the real credentials were never in scope for the in-process SDK client.

Impact

Evidence for the precedence claim, from the outgoing request body (claude 2.1.218, ai-run-sso):

settings model ANTHROPIC_MODEL request body
claude-haiku-4-5 claude-sonnet-4-6 claude-haiku-4-5
claude-haiku-4-5 claude-sonnet-5 claude-haiku-4-5
(none) claude-sonnet-4-6 claude-opus-5 → 400 budget
(none) claude-sonnet-5 claude-opus-5 → 400 budget
claude-sonnet-5 claude-sonnet-5 claude-sonnet-5

User-visible behaviour change: the configured model now actually takes effect. Anyone who had a model pinned in ~/.claude/settings.json, or who was silently running on the default tier, will see their profile's model used instead — with the corresponding cost and quality difference. Worth calling out in release notes.

Test results:

Suite Before After
unit 3950 passed 3955 passed
cli 263 passed 272 passed
agent 8 failed / 6 files 2 failed / 2 files

TC-020 and TC-021 now pass — they assert the session uses the profile's model, which is exactly what was broken, so they were correctly failing before.

Known remaining failures (pre-existing, not addressed here)

  • TC-024/model switch session emits no metrics file. Present in every pre-fix run; verified unaffected by this change.
  • TC-029 — setup wizard needs a real interactive browser SSO login against a fresh temp home; cannot pass unattended.

Both were also verified to fail identically with #505 reverted, so neither is a regression from this release.

Checklist

  • Self-reviewed
  • Manual testing performed (end-to-end request/response inspection with CODEMIE_DEBUG=true)
  • Documentation updated — not done; the ANTHROPIC_MODEL mapping in claude.plugin.ts:89,317 is now supplemented by --model and may warrant a note
  • No breaking changes — behaviour change, see Impact above

https://claude.ai/code/session_01Ce5td85FtwMh65FG66LWaK

8nevil8 and others added 3 commits August 31, 2026 21:03
Two harness defects made the agent suite fail locally regardless of the
product state. Together they accounted for 4 of 8 failures on main.

1. Empty-string env coalescing. CodeMie exports its full CODEMIE_* block
   into the shell of every agent session it launches, and the
   anthropic-subscription provider deliberately blanks CODEMIE_MODEL so the
   Claude CLI falls back to its own defaults. Running the suite from such a
   shell inherited CODEMIE_MODEL='', and `??` only falls back on
   null/undefined - so the empty string beat the intended default and every
   generated profile was written with no model, failing with
   "Configuration incomplete / Missing: model".

   Adds getTestEnvValue/getCodemieTestUrl/getCodemieTestModel, which treat
   empty and whitespace-only values as absent. CI_CODEMIE_URL now also falls
   back to CODEMIE_URL, so no .env.test.local is needed when the suite runs
   inside a CodeMie-launched shell.

2. SSO credentials never reached the test workers. CREDENTIALS_DIR and
   FALLBACK_FILE in src/utils/security.ts are module-level constants resolved
   from CODEMIE_HOME at import time, and the agent vitest project points
   CODEMIE_HOME at a throwaway temp home. The in-process SDK client therefore
   failed with "SSO authentication required" even though global setup had
   just validated the same credentials. The agent-assistant and agent-skills
   suites now copy the credentials into that temp home first.

Adds tests/integration/test-env-helpers.test.ts covering the coalescing
rules with synthetic variable names, so the assertions hold whether or not a
developer has a .env.test.local.

agent-model.test.ts is deliberately left untouched: the pre-commit hook runs
vitest on every staged test file, and TC-020/TC-021/TC-024 still fail there
for unrelated reasons (a sonnet-configured profile records a haiku model).
Those are not masked here.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Claude-Session: https://claude.ai/code/session_01Ce5td85FtwMh65FG66LWaK
…ured

The model chosen in `codemie setup` never reached the wire for Claude.

claude.plugin.ts maps the profile's model onto ANTHROPIC_MODEL only, but
Claude Code ignores that variable. Its actual precedence is:

  --model  >  ~/.claude/settings.json "model"  >  its own default tier

Verified against claude 2.1.218 on an ai-run-sso profile by inspecting the
outgoing request body:

  settings "model"      ANTHROPIC_MODEL       request body
  claude-haiku-4-5      claude-sonnet-4-6     claude-haiku-4-5
  claude-haiku-4-5      claude-sonnet-5       claude-haiku-4-5
  (none)                claude-sonnet-4-6     claude-opus-5   -> 400 budget
  (none)                claude-sonnet-5       claude-opus-5   -> 400 budget
  claude-sonnet-5       claude-sonnet-5       claude-sonnet-5

So a developer with "model" pinned in ~/.claude/settings.json silently ran
every session on that model, and one without it silently ran on the default
opus tier - in both cases ignoring the configured profile. The proxy and the
metrics were accurate throughout; they faithfully recorded the model the
binary actually asked for.

Fix: inject `--model $CODEMIE_MODEL` in the claude enrichArgs hook, next to
the existing --plugin-dir injection, using the same "already present" guard so
an explicit --model / --model= in argv still wins.

A blank CODEMIE_MODEL is treated as absent rather than forwarded as an empty
flag: anthropic-subscription deliberately blanks it so the Claude CLI applies
its own defaults.

This makes TC-020 and TC-021 pass - they assert that the session uses the
model from the profile, which is exactly the behaviour that was broken.
TC-024 and TC-029 still fail; both need a live interactive PTY flow and are
unrelated to model selection.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Claude-Session: https://claude.ai/code/session_01Ce5td85FtwMh65FG66LWaK
CI on this PR failed on Ubuntu with every test passing:

  Test Files  264 passed (264)
  Tests  3953 passed | 2 skipped (3955)
  ⎯⎯⎯ Unhandled Errors ⎯⎯⎯
  Error: ENOENT: no such file or directory,
  open '/tmp/metrics-upload-XXXX/logs/debug-2026-09-01.log'
  ##[error]Process completed with exit code 1

fs.createWriteStream opens the file asynchronously, so a logs directory that
disappears between the mkdir and the open surfaces as an 'error' event on the
stream - never as a throw that initializeLogFile's try/catch can see. With no
listener attached Node treats it as an unhandled error and takes the whole
process down.

The trigger in CI is metrics-upload-contract.test.ts, which points CODEMIE_HOME
at a temp dir in beforeEach and rmSync's it in afterEach, but the same shape
applies in production: any ephemeral CODEMIE_HOME (a sandbox, a container that
cleans up under us) could kill the CLI purely because logging failed. Logging
must never be fatal.

Fix: attach an 'error' listener that drops the stream and degrades to
console-only logging.

The regression test occupies the log path with a directory so the async open
fails deterministically with EISDIR, rather than racing the ENOENT timing. It
was verified against the unmodified logger, where it reproduces the same
"Unhandled Errors" block that failed CI.

Unit suite: 265 files, 3956 tests passing.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Claude-Session: https://claude.ai/code/session_01Ce5td85FtwMh65FG66LWaK
@8nevil8
8nevil8 merged commit 2d94ed9 into main Sep 1, 2026
5 checks passed
@8nevil8
8nevil8 deleted the test/agent-env-hardening branch September 1, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant