Skip to content

fix(acp): materialise only the running provider's file secrets - #4927

Merged
simonrosenberg merged 3 commits into
mainfrom
acp-scope-file-secrets
Sep 11, 2026
Merged

simonrosenberg merged 3 commits into
mainfrom
acp-scope-file-secrets

Conversation

@simonrosenberg

@simonrosenberg simonrosenberg commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

HUMAN:

materialise only the running provider's file secrets- #4927


AGENT:

Closes #4923

Why

ACPAgent.acp_file_secrets defaults to default_acp_file_secrets() — the union across every registered provider — and _materialise_file_secrets iterated all of it regardless of which harness the conversation actually runs. So registering a harness with a file_secrets spec changed behaviour for conversations running a completely different one. 1.45.0 grew that union by two (KIMI_CODE_CONFIG_TOML, PI_AUTH_JSON).

I filed this from reading the code. Running it against the real CLIs turned out worse than the issue described.

Live, on claude-agent-acp (real subprocess, real ACP handshake), with all four reserved blobs in the secret registry:

main this PR
blobs reaching the agent as env vars none all four
dir vars set in the CLI's environment CODEX_HOME, GOOGLE_APPLICATION_CREDENTIALS, KIMI_CODE_HOME, PI_CODING_AGENT_DIR none
credential files written to disk codex/auth.json, gemini-cli/gcloud-credentials.json, kimi-code/config.toml, pi/auth.json none

So today a Claude Code conversation is handed Pi's and Kimi's data-dir variables and has their credential files seeded next to it — and the secrets the user actually set stop arriving as env vars, because _present_file_secret_names excludes every configured name from env injection and from the <CUSTOM_SECRETS> advertisement.

Summary

_active_file_secrets() filters by ownership: drop the specs that are another registered provider's reserved credential, keep everything else.

Scope: this changes credential-file handling, not credential access. A secret is never withheld from the subprocess — it stops being written to disk as a credential file (with that provider's data-dir env var pointed at it) and instead travels the ordinary env-var channel, which is where it already went before its owning provider was registered. A Claude conversation stops creating Codex/Pi credential files; a supplied CODEX_AUTH_JSON still reaches its subprocess as $CODEX_AUTH_JSON and is advertised in <CUSTOM_SECRETS>.

  • The running provider is resolved by the existing _resolved_provider() — acp_server first, then detect_acp_provider_by_command for a custom or aliased command.
  • A spec for a CLI outside the registry is owned by nobody, so it always applies. Downstream applications supplying their own acp_file_secrets for a custom ACP server are unaffected.
  • A reserved name several providers share is kept: owned_elsewhere subtracts the running provider's own names first.
  • An unrecognised server keeps every spec — without an identity we cannot tell whose credential a reserved name belongs to. Same conservative fallback _strip_conflicting_env already documents.
  • Routed the four consumers through it: _materialise_file_secrets, _present_file_secret_names, restart_for_updated_credentials, and the _preconfigured_credentials auth-fallback log.

Deliberately no provenance test. An earlier revision decided whether the list was "the built-in default" by comparing it against default_acp_file_secrets(). That is not stable: a persisted conversation carries the union as it was when written, so after an upgrade an older list no longer equals today's default, is read as a caller override, and silently stops scoping — the same bug, reintroduced. Filtering by ownership needs no such distinction and no migration.

How to Test

Real e2e against the installed CLIs. Spawns the actual codex-acp / claude-agent-acp binaries through the real _start_acp_server path (real handshake — Claude reaches set_session_mode, Codex reaches its auth gate), spying on asyncio.create_subprocess_exec to read back the env the subprocess was launched with, plus what landed under <persistence_dir>/acp/:

--- codex --- (this PR)
  blobs as env    : ['GOOGLE_APPLICATION_CREDENTIALS_JSON', 'KIMI_CODE_CONFIG_TOML', 'PI_AUTH_JSON']
  dir vars set    : ['CODEX_HOME']
  credential files: ['codex/auth.json']

--- claude-code --- (this PR, handshake ok)
  blobs as env    : ['CODEX_AUTH_JSON', 'GOOGLE_APPLICATION_CREDENTIALS_JSON', 'KIMI_CODE_CONFIG_TOML', 'PI_AUTH_JSON']
  dir vars set    : []
  credential files: []

Codex still materialises its own auth.json and gets CODEX_HOME; the other three providers' blobs stay ordinary env vars. On main the same probe produces the four-file / four-dir-var row in the table above for both providers.

Unit. TestACPFileSecretProviderScoping — 16 tests: per-provider scoping, an unrecognised command keeping every spec, command-derived resolution, out-of-registry specs always applying, a shared name being kept, [] staying empty, round-trip survival, the cross-version upgrade case, and two end-to-end cases through _start_acp_server (another provider's blob stays a plain env var; the running provider's still materialises with its dir var). The disjointness test is parametrized off ACP_PROVIDERS, so a provider added upstream is covered without editing it.

test_a_list_persisted_before_a_provider_was_added_still_scopes pins the regression the review caught — it fails against the provenance-comparison revision:

AssertionError: assert {'CODEX_AUTH_JSON', 'GOOGLE_APPLICATION_CREDENTIALS_JSON'} == set()

Suites. tests/sdk/agent/ + tests/sdk/settings/ + tests/agent_server/test_credential_binding.py → 1009 passed. ruff check, ruff format --check and pyright clean on both changed files.

Notes

Context: #4841 (registering a harness upstream must not change a consuming deployment). This is the one instance of that coupling that lives in the SDK's own default rather than in a client's provider list, so neither OpenHands/enterprise#336 nor OpenHands/OpenHands#17228 covers it.

🤖 Generated with Claude Code


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python-slim amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:56930cd-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-56930cd-python \
  ghcr.io/openhands/agent-server:56930cd-python

All tags pushed for this build

ghcr.io/openhands/agent-server:56930cd-golang-amd64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-golang-amd64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-golang-amd64
ghcr.io/openhands/agent-server:56930cd-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:56930cd-golang-arm64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-golang-arm64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-golang-arm64
ghcr.io/openhands/agent-server:56930cd-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:56930cd-java-amd64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-java-amd64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-java-amd64
ghcr.io/openhands/agent-server:56930cd-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:56930cd-java-arm64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-java-arm64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-java-arm64
ghcr.io/openhands/agent-server:56930cd-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:56930cd-python-amd64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python-amd64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python-amd64
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:56930cd-python-arm64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python-arm64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python-arm64
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:56930cd-python-slim-amd64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python-slim-amd64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python-slim-amd64
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-amd64
ghcr.io/openhands/agent-server:56930cd-python-slim-arm64
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python-slim-arm64
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python-slim-arm64
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim-arm64
ghcr.io/openhands/agent-server:56930cd-golang
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-golang
ghcr.io/openhands/agent-server:acp-scope-file-secrets-golang
ghcr.io/openhands/agent-server:56930cd-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:56930cd-java
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-java
ghcr.io/openhands/agent-server:acp-scope-file-secrets-java
ghcr.io/openhands/agent-server:56930cd-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:56930cd-python-slim
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python-slim
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python-slim
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-slim
ghcr.io/openhands/agent-server:56930cd-python
ghcr.io/openhands/agent-server:56930cd39f43d117b864a482916e1178a21796f9-python
ghcr.io/openhands/agent-server:acp-scope-file-secrets-python
ghcr.io/openhands/agent-server:56930cd-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., 56930cd-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 56930cd-python-amd64) are also available if needed

Scope acp_file_secrets to the resolved provider, so registering a harness
upstream cannot change how another provider's conversation delivers a secret
that carries the new reserved name. A caller-supplied list and an
unrecognised server both keep today's behaviour.

Closes #4923

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor
  ✅ **PR Artifacts Cleaned Up**

  The `.pr/` directory is no longer present.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result: ✅ PASSED

Action log

@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 75a603598e040d756ff50f1446631419de958b12
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/007a35c5-3363-4b1c-ad00-988cb83dbac6

This comment was posted by an AI agent (OpenHands).

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result: ✅ PASSED

Action log

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/agent
   acp_agent.py165114591%319–320, 467–468, 495, 533, 703, 709, 792, 832, 1051–1053, 1110–1111, 1191, 1211, 1442–1443, 1492, 1494, 1498, 1502, 1525, 1588–1589, 1594, 1661, 1850, 1954, 1966, 1979, 2047, 2050–2051, 2068–2069, 2085, 2102, 2107, 2215, 2220, 2269–2270, 2272, 2518–2521, 2526, 2670, 2766, 2792–2793, 2809–2810, 2853–2854, 3081–3082, 3086–3088, 3091–3096, 3100–3102, 3106, 3110–3111, 3114–3115, 3118, 3400, 3415–3416, 3419–3421, 3429, 3433, 3444–3445, 3453, 3457–3458, 3461, 3509, 3514–3516, 3520–3521, 3553, 3590–3591, 3642, 3857–3859, 3862–3863, 3909, 4071, 4079–4081, 4124–4125, 4128, 4136–4138, 4145, 4147, 4151, 4154, 4163–4165, 4172, 4208–4209, 4227–4230, 4233, 4237–4239, 4241, 4245–4246, 4515–4516, 4529–4530, 4586–4587
TOTAL420411174672% 

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

🔴 Needs improvement

The provider scoping itself is the right direction, but inferring whether acp_file_secrets was caller-supplied by comparing it with the current registry default is not stable across serialization or SDK upgrades. See the inline finding.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM

This changes credential routing and disk materialization. The new provider-specific path is well tested for fresh/current configurations, but older persisted defaults can still route unrelated credential blobs as file secrets after upgrade, and an explicit current-union override is silently ignored. A human maintainer should validate the persistence policy before merge; this runtime behavior change should remain a COMMENT review pending that decision.

VERDICT:
❌ Needs rework: The configuration needs durable provenance/policy rather than value-based default detection.

KEY INSIGHT:
A mutable registry-derived value cannot reliably encode whether a persisted list was an implicit default or an explicit override.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it is merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.

Comment thread openhands-sdk/openhands/sdk/agent/acp_agent.py Outdated
Filter by spec ownership instead of testing whether acp_file_secrets is the
built-in default. A persisted conversation carries the union as it was when
written, so comparing against today's default read an older list as a caller
override and stopped scoping after an upgrade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: cf1283ca0690d333cbdf0bd7d90b42d16583cb58
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/105e7f07-3ce5-45b7-8452-3bba14f34175

This comment was posted by an AI agent (OpenHands).

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

🟢 Good taste

No material findings. The ownership-based filter fixes the previous persistence/provenance issue without adding serialized policy state, and the focused provider-scoping tests pass locally (16 passed).

The PR description statements that caller-supplied acp_file_secrets are returned verbatim and that defaults are recognized by value are now stale after cf1283ca; please update the AGENT section so it describes the ownership-based policy. Do not modify the human-only HUMAN: field on behalf of its author.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM

This changes credential routing, environment injection, and on-disk materialization for ACP subprocesses. The implementation and regression coverage are sound, but it can affect agent execution behavior, so per repository policy this remains a COMMENT review for a human maintainer to decide after lightweight eval validation.

VERDICT:
✅ Worth merging: Core logic is sound; human maintainer confirmation is appropriate because this changes ACP runtime behavior.

KEY INSIGHT:
Filtering by registry ownership is stable across persisted historical defaults and avoids unreliable provenance inference.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing. See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it is merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.


@simonrosenberg simonrosenberg self-assigned this Sep 9, 2026
@simonrosenberg
simonrosenberg requested review from all-hands-bot and removed request for all-hands-bot September 9, 2026 14:31
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 56930cd39f43d117b864a482916e1178a21796f9
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/f63a8e4f-93fe-4ed8-9b39-60f7f3f18b0f

This comment was posted by an AI agent (OpenHands).

@all-hands-bot

Copy link
Copy Markdown
Collaborator

⚠️ OpenHands PR Reviewer encountered a problem at commit 56930cd39f43 (status: error).

This comment was posted by an AI agent (OpenHands).

@all-hands-bot
all-hands-bot removed their request for review September 10, 2026 21:46
@simonrosenberg
simonrosenberg merged commit 57f5cc9 into main Sep 11, 2026
49 of 50 checks passed
@simonrosenberg
simonrosenberg deleted the acp-scope-file-secrets branch September 11, 2026 16:03
sohamb117 pushed a commit to sohamb117/openhands-software-agent-sdk that referenced this pull request Sep 12, 2026
…ands#4927)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: allhands-bot <allhands-bot@users.noreply.github.com>
@github-actions github-actions Bot mentioned this pull request Sep 15, 2026
6 of 7 tasks
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.

ACPAgent materialises every provider's file secrets, so registering a harness changes behaviour for all the others

3 participants