Skip to content

fix(host): model Claude Code command settings and endpoint env keys - #1083

Open
pengfei-threemoonslab wants to merge 1 commit into
mainfrom
claude/review-1027-host-executable-settings
Open

pengfei-threemoonslab wants to merge 1 commit into
mainfrom
claude/review-1027-host-executable-settings

Conversation

@pengfei-threemoonslab

Copy link
Copy Markdown
Contributor

Summary

  • Closes host diff: model statusLine, apiKeyHelper and endpoint env keys; no false 'no changes' #1027
  • Command settings are grants. Host-grants 0.9 (extended in place; unreleased) gains command_setting grants for statusLine, subagentStatusLine, fileSuggestion, apiKeyHelper, otelHeadersHelper, awsAuthRefresh and awsCredentialExport: rated execute/high like a settings hook, the whole value digested, the command published as a hook handler's is (executable name, digest, shape; never text). Adding one is a ⚠ high added row; editing one is a changed row naming the two digests, direction unknown.
  • Endpoint, trust and loader env entries are grants. env grants for *_BASE_URL, OTEL_EXPORTER_OTLP_…ENDPOINT, HTTP(S)_PROXY, NODE_EXTRA_CA_CERTS, NODE_TLS_REJECT_UNAUTHORIZED, NODE_OPTIONS, BASH_ENV, CLAUDE_CODE_SHELL_PREFIX, LD_PRELOAD, DYLD_INSERT_LIBRARIES: high, keyed by name, value digested and never published. Every other env value stays redacted (rotation quiet).
  • Safety net. A parsed Claude Code settings artifact publishes display-only unmodelled_keys (key + digest; out of artifact equality, inventory digests and saved baselines). A zero-row coverage item names the ones that changed in detail (keys this entry does not compare: …), and a comparable comparison with no row in which a settings, MCP or hook file changed headlines Changed, not compared: <file> (<keys>). No host-grant row describes this change. No verdict is implied. in diff, verify text and the PR comment instead of No static host-grant changes detected.
  • Coverage partial. A settings key no reader models that is shaped as a command (an object with a string command, or a …Helper string) is a blocking unsupported issue, as policyHelper already is, so audit --host reports Claude Code partial.
  • Coverage note corrected. (redacted values, such as env values this entry does not model, are not compared); it no longer blames redaction for apiKeyHelper/statusLine.
  • Cold-start oracle (benchmark/cold-start/expected.py, score.py): the seven command settings and the modelled env names move from unsupported to supported (new command_setting/env change kinds); forceLoginMethod, forceLoginOrgUUID and other env names stay unsupported. No vendored case's replay outcome changes.
  • Docs: docs/host-boundary-support.md (support matrix, new "Claude Code commands and env" section, known unread surfaces), STABILITY migration note #host-commands-env-1027, CHANGELOG, quickstart/README/examples headline text, docs/distribution-surfaces.md capability_diff row.

Type

  • Check or risk-model change
  • Input adapter change
  • CLI or GitHub Action behavior
  • Report, schema, or SARIF output
  • Documentation only

Authorship

  • A coding agent wrote some or all of this change. Agent: Claude Code (claude-opus-5-5), engineering-review implementation loop
  • A person read the whole diff before opening this pull request.

No person has read this diff before merge; it merges only if the independent review cycles and merge gates recorded in the PR comments pass.

Verification

CI is authoritative for python -m ruff check ., python -m compileall -q src tests, and python -m pytest.

Additional local checks run (Python 3.14, PYTHONPATH=src:.):

  • python -m ruff check . — All checks passed.
  • python -m compileall -q src tests — clean.
  • python scripts/generate_schemas.py --check — clean (only docs/host-grants-{inventory,baseline,drift}-schema.v0.9.json changed, regenerated in this PR; verifier-schema.v0.21.json is byte-identical).
  • python scripts/regenerate_goldens.py --check — Checked 24 sample artifacts; 0 changed.
  • python scripts/verify_dependency_lock.py — OK.
  • New tests/test_claude_command_settings.py (14 tests): fails before the change (13 of 14 fail against origin/main source; the control test for an unchanged unmodelled key passes on both), passes after. Shard weight added (21.0 s).
  • Targeted: tests/test_host_comparison_coverage.py, test_capability_diff.py, test_host_audit.py, test_check_unmodelled_host_config_keys.py, test_host_boundary_unread_surfaces.py, test_hook_args_and_settings.py, test_host_diff_review_changes.py, test_host_grant_direction.py, test_host_inventory_stability.py, test_host_local_precedence.py, test_host_only_advisory_recipe.py, test_prompt_disabling_settings.py, test_host_settings_narrowing_review.py, test_claude_hook_loading_evidence.py, test_partial_host_comparison.py, test_schema_boundaries.py, test_schema_roundtrip.py, test_distribution_surface_parity.py, test_host_diff_entry_docs.py, test_cold_start_replay.py, test_unread_changed_inputs.py, test_host_path_privacy.py, test_mcp_env_var_names.py — all pass after the updates in this PR (the pre-existing assertions this change intentionally moves were updated: the zero-row env/apiKeyHelper cases that are now rows, the redacted-values note, and the headline on zero-row settings changes).
  • Second batch (test_cold_start_replay.py, test_host_config_replay.py, test_host_config_oracle_controls.py, test_workflow_agent_launches.py, test_host_boundary_check.py, test_manifest_free_pr_rows.py, test_agent_control_envelope_rows.py, test_host_change_route_parity.py, test_hook_mcp_detail_fields.py, test_hook_command_shape.py, test_host_file_links.py, test_linked_unchanged_limits.py, test_unchanged_host_limits.py, test_enabled_plugin_hook_routing.py, test_claude_known_marketplaces.py, test_host_link_read_through.py, test_rule_credential_redaction.py, test_hook_script_comparison_limits.py, test_claude_permission_rule_model.py, test_inline_hook_allow.py, test_mcp_host_attribution.py, test_oversized_instruction_document_partial.py, test_reader_vocabulary.py, test_public_surface_contract.py, test_claude_command_settings.py) — all pass; the cold-start and host-config replays reproduce their recorded replay.json outcomes unchanged.
  • Full suite (-m "not perf and not slow", the four documented ignores), serial, local: started in the background after push (about three hours serially here); CI's six shards are authoritative, and the result will be reported in the review comment.
  • ./shipgate verify --workspace . --config shipgate.yaml --base c6bb59ce --head HEAD --json — control complete, all permissions true, release_decision.decision: passed.
  • Manual: the issue's reproduction (statusLine, apiKeyHelper, env.NODE_OPTIONS, env.ANTHROPIC_BASE_URL added to .claude/settings.json) now prints four ⚠ high added rows and 4 change(s), 4 widening; none of the values appears in any output. check on the same change still answers require_review (unchanged).

Release-readiness notes

  • No user-code import added to default scan paths
  • No network access added to default scan paths
  • New or changed check IDs are documented in docs/checks.md
  • Report/schema changes are additive or documented in STABILITY.md

Decisions taken

  • Host-grants 0.9 extended in place, no 0.10. 0.9 has not shipped in a tagged release (1.2.0 shipped 0.7), and every other 0.9 addition was made in place; the issue's risk note (bump if the kind set changes, add a migration note) is met by the in-place pattern plus STABILITY.md#host-commands-env-1027.
  • Two grant kinds, not a reuse of permission_mode/hook. permission_mode publishes its value as text, which would publish the command; hook carries a loading basis and handler list that do not apply.
  • Which env names. The issue names ANTHROPIC_BASE_URL, other *_BASE_URL and NODE_OPTIONS "…"; I added the documented proxies and OTel exporter endpoints (where requests go), the two Node TLS trust switches, and the code-loading names (BASH_ENV, CLAUDE_CODE_SHELL_PREFIX, LD_PRELOAD, DYLD_INSERT_LIBRARIES). Secret-named and non-plain names are never modelled. The list lives once in core/host_settings.py; the oracle restates it from the docs and a test checks they agree.
  • Direction. Added → expansion (as a settings hook or additional path); edited → changed, direction unknown (as a hook command edit); removed → removal.
  • Precedence. Both kinds merge across settings layers (every layer's grant kept, never shadowed): the stack does not document env merging, and keeping both over-reports rather than hides.
  • No new coverage status. Verifier 0.21 shipped in 1.2.0, so a new status literal would need verifier 0.22/contract churn. Instead the unmodelled keys ride in the existing optional detail on changed_without_grant_change/changed_without_rows items (the published schema already allows it; only the model's cross-field validator is widened, recorded in a schema-frozen note).
  • "Partial coverage" for AC2. Expressed in the comparison as the item naming the keys not compared plus the Changed, not compared headline. Making every unknown key a blocking limit would refuse every comparison of a settings file holding model or $schema; only command-shaped unmodelled keys are blocking (AC3), matching the policyHelper precedent.
  • Headline scope. "All hosts" is applied to settings, MCP and hook files (any host) that changed with no row — including a redacted env value edit, a reformat or a reordered rule, which the data cannot tell from a redacted value change. Workflows and instruction documents keep their existing headline/coverage line; changed_not_read (Name relevant inputs the host entry does not read, so a zero-row result is never read as covered (#812 slice 2) #821) is unchanged.
  • unmodelled_keys is display-only (artifact equality, inventory digests and saved baselines leave it out) so an existing baseline does not drift merely because the member now exists; redacted_sha256 already moves whenever it does.

Protected paths touched

none

Follow-up candidates

  • Workflow zero-row headline. A workflow change with no row (for example an added unread agent step) still headlines No static host-grant changes detected, as docs/host-boundary-support.md:615 documents; this PR scoped the new headline to settings, MCP and hook files (report/host_comparison.py _SETTINGS_SOURCE_KINDS).
  • changed_not_read (Name relevant inputs the host entry does not read, so a zero-row result is never read as covered (#812 slice 2) #821) headline. A comparison whose only change is a changed input no reader reads still headlines No static host-grant changes detected beside its changed_not_read item (tests/test_unread_changed_inputs.py:224 pins it).
  • Scripts a command setting runs are not read. Hook scripts are bound and compared (A hook command that runs an in-repo script: editing the script changes what runs, with no row #702), but _bind_hook_scripts binds only kind == "hook" (core/host_grants.py:5174), so an edit only to ./status.sh behind statusLine gives no row (documented as a limit in this PR).
  • Workflow settings input vs the settings reader. docs/host-boundary-support.md:541 says a claude-code-action settings input publishes apiKeyHelper as <redacted> "as the host readers redact them"; the settings reader now digests it, so a rotation there is quiet while the same edit in .claude/settings.json is a row.
  • disabledMcpjsonServers. The cold-start oracle counts it as supported (benchmark/cold-start/expected.py:37, :155) but no reader models it (core/host_settings.py:82 lists only enabledMcpjsonServers); it is now at least named in unmodelled_keys.
  • URL path in an env endpoint digest. The env grant digests the value as every grant input is held, so a path-only change to ANTHROPIC_BASE_URL is no row (same class as An MCP server URL's path is outside its change digest: /read → /admin produces no row #772).

…lse "no changes" (#1027)

Claude Code runs statusLine.command and apiKeyHelper as shell commands, and
env entries such as ANTHROPIC_BASE_URL or NODE_OPTIONS redirect its requests
or load code. No reader published a grant for them, so diff, verify's host
comparison and the PR comment headlined "No static host-grant changes
detected" for such an edit, and audit --host reported Claude Code coverage
complete.

Host-grants 0.9 (extended in place) gains command_setting grants for
statusLine, subagentStatusLine, fileSuggestion, apiKeyHelper,
otelHeadersHelper, awsAuthRefresh and awsCredentialExport (execute/high, the
command published as a hook's is) and env grants for endpoint, trust and
loader names (high, value digested, never published). A settings artifact
publishes display-only unmodelled_keys; a coverage item names the ones that
changed in its detail, and a comparable comparison with no row in which a
settings, MCP or hook file changed headlines "Changed, not compared". A key
no reader models that is shaped as a command is a blocking limit, so
audit --host reports coverage partial, as for policyHelper. The cold-start
oracle moves the modelled keys to supported.
@pengfei-threemoonslab pengfei-threemoonslab added engineering-review Filed from the 2026-10-12 comprehensive engineering review P0 Release-blocking: correctness of the gate or the primary adoption path labels Oct 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engineering-review Filed from the 2026-10-12 comprehensive engineering review P0 Release-blocking: correctness of the gate or the primary adoption path

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host diff: model statusLine, apiKeyHelper and endpoint env keys; no false 'no changes'

1 participant