What happens
A generic-ACP agent configured the documented way — an entry under acp.agents:
in ~/.omnigent/config.yaml, or via omnigent setup → Custom ACP agent — is
spawned with no provider credentials at all, and there is no way to give it
any. The agent starts unauthenticated, stalls during the ACP handshake, and the
turn fails.
This is the underlying failure behind #4281 (whose visible symptom, the blank
inner executor error: , is the separate reporting bug PR #4362 addresses).
It explains every clue in that report: the failure is not agent-specific, it is
unaffected by omnigent_mcp: false, and the same agent is healthy when driven
directly over stdio — where it inherits the user's real shell environment.
Root cause
AcpExecutor._build_spawn_env filters deny-by-default with an empty prefix
family (omnigent/inner/acp_executor.py):
return clean_agent_env(
allow_prefixes=(),
extra_allowed=declared_passthrough(self._os_env),
)
allow_prefixes=() is a deliberate choice from #3479 — the executor drives an
arbitrary agent, so it cannot know which vendor family that agent
authenticates with, and guessing would re-widen the leak that issue closed. The
commit message says so explicitly:
acp none - base + env_passthrough only, the agent is arbitrary
The gap is the other half of that sentence. env_passthrough is the intended
escape hatch, and for generic ACP it is unreachable from the agent's own
config:
AcpAgentConfig has no env field.
AcpAgentEntry (the parsed acp.agents: row) has no env field, so
acp_agents() cannot read one and acp_agents_settings() cannot persist one.
_build_acp_spawn_env therefore has nothing to forward.
acp_harness._resolve_os_env() falls back to
OSEnvSandboxSpec(type="none"), i.e. env_passthrough=None.
os_env.sandbox.env_passthrough only exists on a full agent spec. A user
configuring an ACP agent through acp.agents: never authors one, so in practice
the hatch does not exist for the documented configuration path.
Contrast the sibling executors, which each get their family plus a
credential resolver:
| executor |
allow_prefixes |
provider env |
goose_executor |
("GOOSE_",) |
_provider_env() |
qwen_executor |
("QWEN_", "OPENAI_", "DASHSCOPE_") |
_resolve_gateway_env() |
acp_executor |
() |
none |
Reproduction
Measured against a realistic environment — only HOME, PATH, TERM survive:
from omnigent.inner.agent_env import clean_agent_env
fake = {"HOME": "/h", "PATH": "/usr/bin", "TERM": "xterm",
"XAI_API_KEY": "...", "OPENAI_API_KEY": "...", "ANTHROPIC_API_KEY": "...",
"GOOSE_PROVIDER": "anthropic", "GOOSE_MODEL": "sonnet"}
clean_agent_env(allow_prefixes=(), extra_allowed=(), source=fake)
# -> {'HOME': ..., 'PATH': ..., 'TERM': ...}
# stripped: ANTHROPIC_API_KEY, GOOSE_MODEL, GOOSE_PROVIDER, OPENAI_API_KEY, XAI_API_KEY
End to end against a fake ACP agent that answers initialize offline but
contacts its provider on session/new (so it stalls without a key), with the
user's key exported in their shell — one variable changed:
default (no env_passthrough) FAILED -> 'inner executor error: ' (blank)
env_passthrough=['XAI_API_KEY'] SUCCEEDED (turn completed)
Impact
Every generic-ACP agent configured through acp.agents: / omnigent setup is
unusable if it authenticates from an environment variable, with no in-product
workaround. Also blocks #3460 (the ACP model picker): the advertised model list
never arrives because the turn dies at session/new.
There is currently no test covering the generic-ACP spawn env, which is how
this regressed silently.
Proposed fix
Keep deny-by-default; make the escape hatch reachable per agent. Add an
explicit, user-declared allowlist to the ACP agent config, threaded through the
existing plumbing:
acp:
agents:
- name: Grok Build
command: grok agent stdio
env_passthrough: [XAI_API_KEY]
Names only, never values — the variable is read from the host environment at
spawn, so no secret is written into config.yaml. AcpAgentEntry →
_build_acp_spawn_env → a new HARNESS_ACP_ENV_PASSTHROUGH → AcpAgentConfig
→ _build_spawn_env's extra_allowed, unioned with any spec-declared
os_env.sandbox.env_passthrough.
Rejected alternative: widening BASE_ALLOW_* or adding a guessed prefix family
for ACP. That hands every configured ACP agent every other provider's key and
re-opens #3445 for the one executor that cannot know what it is running.
Environment
- Omnigent
main @ 9dab48b46
What happens
A generic-ACP agent configured the documented way — an entry under
acp.agents:in
~/.omnigent/config.yaml, or viaomnigent setup→ Custom ACP agent — isspawned with no provider credentials at all, and there is no way to give it
any. The agent starts unauthenticated, stalls during the ACP handshake, and the
turn fails.
This is the underlying failure behind #4281 (whose visible symptom, the blank
inner executor error:, is the separate reporting bug PR #4362 addresses).It explains every clue in that report: the failure is not agent-specific, it is
unaffected by
omnigent_mcp: false, and the same agent is healthy when drivendirectly over stdio — where it inherits the user's real shell environment.
Root cause
AcpExecutor._build_spawn_envfilters deny-by-default with an empty prefixfamily (
omnigent/inner/acp_executor.py):allow_prefixes=()is a deliberate choice from #3479 — the executor drives anarbitrary agent, so it cannot know which vendor family that agent
authenticates with, and guessing would re-widen the leak that issue closed. The
commit message says so explicitly:
The gap is the other half of that sentence.
env_passthroughis the intendedescape hatch, and for generic ACP it is unreachable from the agent's own
config:
AcpAgentConfighas no env field.AcpAgentEntry(the parsedacp.agents:row) has no env field, soacp_agents()cannot read one andacp_agents_settings()cannot persist one._build_acp_spawn_envtherefore has nothing to forward.acp_harness._resolve_os_env()falls back toOSEnvSandboxSpec(type="none"), i.e.env_passthrough=None.os_env.sandbox.env_passthroughonly exists on a full agent spec. A userconfiguring an ACP agent through
acp.agents:never authors one, so in practicethe hatch does not exist for the documented configuration path.
Contrast the sibling executors, which each get their family plus a
credential resolver:
allow_prefixesgoose_executor("GOOSE_",)_provider_env()qwen_executor("QWEN_", "OPENAI_", "DASHSCOPE_")_resolve_gateway_env()acp_executor()Reproduction
Measured against a realistic environment — only
HOME,PATH,TERMsurvive:End to end against a fake ACP agent that answers
initializeoffline butcontacts its provider on
session/new(so it stalls without a key), with theuser's key exported in their shell — one variable changed:
Impact
Every generic-ACP agent configured through
acp.agents:/omnigent setupisunusable if it authenticates from an environment variable, with no in-product
workaround. Also blocks #3460 (the ACP model picker): the advertised model list
never arrives because the turn dies at
session/new.There is currently no test covering the generic-ACP spawn env, which is how
this regressed silently.
Proposed fix
Keep deny-by-default; make the escape hatch reachable per agent. Add an
explicit, user-declared allowlist to the ACP agent config, threaded through the
existing plumbing:
Names only, never values — the variable is read from the host environment at
spawn, so no secret is written into
config.yaml.AcpAgentEntry→_build_acp_spawn_env→ a newHARNESS_ACP_ENV_PASSTHROUGH→AcpAgentConfig→
_build_spawn_env'sextra_allowed, unioned with any spec-declaredos_env.sandbox.env_passthrough.Rejected alternative: widening
BASE_ALLOW_*or adding a guessed prefix familyfor ACP. That hands every configured ACP agent every other provider's key and
re-opens #3445 for the one executor that cannot know what it is running.
Environment
main@9dab48b46