Repository navigation
fix(host): model Claude Code command settings and endpoint env keys - #1083
Open
pengfei-threemoonslab wants to merge 1 commit into
Open
pengfei-threemoonslab wants to merge 1 commit into
pengfei-threemoonslab wants to merge 1 commit into
Conversation
…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.
This branch has not been deployed
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.
Summary
0.9(extended in place; unreleased) gainscommand_settinggrants forstatusLine,subagentStatusLine,fileSuggestion,apiKeyHelper,otelHeadersHelper,awsAuthRefreshandawsCredentialExport: ratedexecute/highlike 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 addedrow; editing one is achangedrow naming the two digests, direction unknown.enventries are grants.envgrants 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 otherenvvalue stays redacted (rotation quiet).unmodelled_keys(key + digest; out of artifact equality, inventory digests and saved baselines). A zero-row coverage item names the ones that changed indetail(keys this entry does not compare: …), and a comparable comparison with no row in which a settings, MCP or hook file changed headlinesChanged, not compared: <file> (<keys>). No host-grant row describes this change. No verdict is implied.indiff,verifytext and the PR comment instead ofNo static host-grant changes detected.command, or a…Helperstring) is a blockingunsupportedissue, aspolicyHelperalready is, soaudit --hostreports Claude Codepartial.(redacted values, such as env values this entry does not model, are not compared); it no longer blames redaction forapiKeyHelper/statusLine.benchmark/cold-start/expected.py,score.py): the seven command settings and the modelledenvnames move from unsupported to supported (newcommand_setting/envchange kinds);forceLoginMethod,forceLoginOrgUUIDand otherenvnames stay unsupported. No vendored case's replay outcome changes.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.mdcapability_diffrow.Type
Authorship
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, andpython -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 (onlydocs/host-grants-{inventory,baseline,drift}-schema.v0.9.jsonchanged, regenerated in this PR;verifier-schema.v0.21.jsonis byte-identical).python scripts/regenerate_goldens.py --check—Checked 24 sample artifacts; 0 changed.python scripts/verify_dependency_lock.py— OK.tests/test_claude_command_settings.py(14 tests): fails before the change (13 of 14 fail againstorigin/mainsource; the control test for an unchanged unmodelled key passes on both), passes after. Shard weight added (21.0 s).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-rowenv/apiKeyHelpercases that are now rows, the redacted-values note, and the headline on zero-row settings changes).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 recordedreplay.jsonoutcomes unchanged.-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— controlcomplete, all permissionstrue,release_decision.decision: passed.statusLine,apiKeyHelper,env.NODE_OPTIONS,env.ANTHROPIC_BASE_URLadded to.claude/settings.json) now prints four⚠ high addedrows and4 change(s), 4 widening; none of the values appears in any output.checkon the same change still answersrequire_review(unchanged).Release-readiness notes
docs/checks.mdSTABILITY.mdDecisions taken
0.9extended in place, no0.10.0.9has not shipped in a tagged release (1.2.0 shipped0.7), and every other0.9addition 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 plusSTABILITY.md#host-commands-env-1027.permission_mode/hook.permission_modepublishes its value as text, which would publish the command;hookcarries a loading basis and handler list that do not apply.envnames. The issue namesANTHROPIC_BASE_URL, other*_BASE_URLandNODE_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 incore/host_settings.py; the oracle restates it from the docs and a test checks they agree.changed, direction unknown (as a hook command edit); removed → removal.envmerging, and keeping both over-reports rather than hides.0.21shipped in 1.2.0, so a newstatusliteral would need verifier0.22/contract churn. Instead the unmodelled keys ride in the existing optionaldetailonchanged_without_grant_change/changed_without_rowsitems (the published schema already allows it; only the model's cross-field validator is widened, recorded in a schema-frozen note).Changed, not comparedheadline. Making every unknown key a blocking limit would refuse every comparison of a settings file holdingmodelor$schema; only command-shaped unmodelled keys are blocking (AC3), matching thepolicyHelperprecedent.envvalue 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_keysis 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_sha256already moves whenever it does.Protected paths touched
none
Follow-up candidates
No static host-grant changes detected, asdocs/host-boundary-support.md:615documents; 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 headlinesNo static host-grant changes detectedbeside itschanged_not_readitem (tests/test_unread_changed_inputs.py:224pins it)._bind_hook_scriptsbinds onlykind == "hook"(core/host_grants.py:5174), so an edit only to./status.shbehindstatusLinegives no row (documented as a limit in this PR).settingsinput vs the settings reader.docs/host-boundary-support.md:541says a claude-code-actionsettingsinput publishesapiKeyHelperas<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.jsonis 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:82lists onlyenabledMcpjsonServers); it is now at least named inunmodelled_keys.envendpoint digest. Theenvgrant digests the value as every grant input is held, so a path-only change toANTHROPIC_BASE_URLis no row (same class as An MCP server URL's path is outside its change digest:/read→/adminproduces no row #772).