deepseek-harness/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.md

10 KiB

Agent Note: Product subagents use Profile-selected non-interactive permissions

Status: implemented

English | 中文

Problem

The Claude Code and Codex product providers run without a human interface. Native permission prompts, user dialogs, or MCP elicitation therefore cannot wait for a person, but relying on either product's ambient default can still select an interactive mode. A deployment also needs to choose broader native modes without giving the parent model or one tool call a way to raise its own authority.

A failed product run previously reached the subagent seam only as a stop reason. Logs could retain the product error, but the foreground parent and a one-shot background Job could not distinguish a permission refusal from another failure. Reusing assistant output for that fact would misattribute infrastructure detail to the child model.

Decision

Each product Provider owns its own Profile-level permissionMode value. The two Config fields deliberately use the products' native names rather than a shared restricted/automatic/full abstraction. The Provider fixes the resolved value for every run from that plugin instance. The subagent tool schema and SubagentStartRequest contain no permission field, so a model or individual delegation cannot change it.

Claude Code

Claude Code defaults to dontAsk and accepts only the native non-interactive modes supported by the pinned Agent SDK:

Value Native behavior
dontAsk Deny operations that are not already authorized instead of prompting.
acceptEdits Accept edits; deny any remaining permission prompt through the unattended callback.
auto Let Claude Code's native classifier allow or deny permission requests.
plan Use planning mode, deny execution approval, and return the completed plan as the final answer.
bypassPermissions Set the SDK's explicit dangerous confirmation and bypass permission checks.

The Provider continues to omit settingSources: an optional instance-level model is a separate direct SDK override, while Claude Code remains the owner of user, project, and local settings, authentication, tools, and sandbox behavior outside the selected mode.

Every query disables AskUserQuestion. Non-bypass permission callbacks deny instead of returning the SDK's indefinitely blocking null; plan mode also places ExitPlanMode in disallowedTools, so native allow rules cannot switch the unattended query back to execution. MCP elicitation is declined; the supported refusal dialog is cancelled; undeclared dialog kinds use the SDK's no-dialog failure behavior. A native permission_denied message records the same operation-local fact. These paths do not create an approval session, queue, cache, or retry loop.

Codex

Codex defaults to never and accepts the three native non-interactive modes exposed by Codex 0.147.0. The Provider starts the fixed app-server command, then maps the selected mode into official thread/start fields because CLI-global permission flags do not configure threads created later by an app-server client:

Value thread/start fields Native behavior
never approvalPolicy: never; sandbox omitted Never prompt; execution failures return to the model under the native sandbox.
approve-for-me approvalPolicy: on-request, approvalsReviewer: auto_review, sandbox: workspace-write Route permission requests through Codex automatic review.
dangerously-bypass-approvals-and-sandbox approvalPolicy: never, sandbox: danger-full-access Skip approval and sandbox enforcement.

The Provider overrides only those thread fields. CODEX_HOME, project configuration, model/provider selection, MCP, hooks, skills, authentication, and sandbox facts not selected by the mode remain native Codex state. The wire still denies any unexpected approval, permission, user-input, or MCP request rather than opening a dynamic allow path.

Failure diagnostic

SubagentResult carries an optional diagnostic for provider-authored, non-assistant failure detail. A Provider removes tool inputs, file contents, environment values, credentials, and raw protocol payloads before producing it. The shared out-of-process result boundary limits the complete text to 4096 UTF-8 bytes and marks truncation without splitting a character. The minimal-diagnostics decision owns Claude Code's non-permission action categories, while the structured failure-facts decision continues to own Codex's current categories; both retain lifecycle stages and process outcomes in the same field.

Each product's permission fact contains only the effective mode, request category, unattended decision, and a fixed safe reason. Claude Code derives those facts from SDK callbacks and permission_denied messages. Codex derives them from app-server requests, declined items, sandboxError, and two fixed permission signatures in a bounded stderr tail; raw stderr is still forwarded to the Host but never copied into the diagnostic. Both Providers place their structured failure line before the latest contributing permission fact. A successful result returns only the strict final answer; local cancellation remains aborted without permission detail; an unpublished startup failure still rejects start(). The Provider never adds either diagnostic fact to assistant output, structured output, or subagent/end.lastAssistantMessage.

The foreground consumer presents the stop-reason headline, then the optional diagnostic, then any partial assistant output. The one-shot background adapter stores the same diagnostic beside the stop reason in the failed Job detail. Providers that omit the field retain their previous behavior.

Ownership and lifecycle

Fact or resource Owner Observable behavior
Profile permission choice Each product Provider Config Invalid, interactive, or unknown values fail during configuration.
Permission and sandbox semantics Claude Code Agent SDK or Codex app-server Each Provider passes one native mode and does not mirror product policy.
Interaction decisions and safe diagnostic One product run Concurrent runs keep independent mode, protocol, and diagnostic state.
Diagnostic type and byte limit dsh-subagent Consumers receive a bounded optional field separate from assistant output.
Foreground and Job presentation dsh-tool-subagent and the generic Job runtime Scheduling choice does not change the underlying failure fact.
Process cancellation and quiescence Product Provider and dsh-subprocess Result settlement still precedes idempotent whole-tree disposal.

Verification

Package tests pin every allowed and rejected Config value, the exact SDK and app-server field mappings, dangerous confirmations, unattended terminal responses, diagnostic sanitization and UTF-8 bound, successful-result omission, concurrent-run isolation, foreground ordering, Job detail, stderr observer disposal, and process cleanup. The real Claude Agent SDK 0.3.237 and Claude Code 2.1.237 fixture proves its safe default, restricted denial, explicit bypass, and whole-tree quiescence. The real Codex app-server fixture proves that thread-level never overrides ambient on-request, automatic review starts, dangerous bypass writes only inside suite-owned temporary storage, fixed stderr signatures produce safe diagnostics, and the wrapper/native tree exits. Loader composition proves non-default modes can be published without starting either product, and the keyless ACP snapshot records each product's failure diagnostic through foreground and Job presentation while the model-facing product tool schemas contain no permission parameter.

Alternatives considered

Use the product's ambient permission default. A native setting may select an interactive mode and make unattended behavior deployment-dependent. The Provider must choose a non-interactive mode explicitly for every query.

Put permission mode in the model-facing tool or each start request. That would let task content select authority and would duplicate a Profile deployment decision on every call.

Copy product settings or map the parent Harness sandbox. The products do not share one permission vocabulary. Mirroring their state would create a second authority and obscure the native sandbox consequences of automatic and bypass modes.

Forward prompts to a parent, Web client, or CLI. The one-shot product run has no owned human-interaction lifecycle. Adding one would require durable request identity, routing, cancellation, and timeout semantics beyond this decision.

Return raw product errors, stderr, or tool inputs. Those values can contain commands, paths, workspace data, environment values, or credentials. A fixed safe diagnostic keeps the failure actionable without exposing the product transcript.

Store a separate Job diagnostic. The Job is only a scheduling adapter for the same SubagentRun; a second field would let foreground and background failure meanings drift.

Consequences

Profiles can select each product's native restricted, automatic, planning/edit-accepting where supported, or bypass behavior before the Provider starts, while both safe defaults never ask a person. Broader modes remain explicit deployment choices and retain their native sandbox consequences.

Permission failures become visible to both foreground parents and one-shot background Jobs without turning infrastructure text into an assistant answer. The same field can also carry the separately owned structured failure facts. It can enter model context, Job notices, API projections, and Job UI through the ordinary consumer paths, so the Provider must sanitize and bound the complete text before result settlement.

The change adds no product session persistence, human approval channel, dynamic permission operation, progress stream, retry policy, or rollback. Other Providers remain valid without producing a diagnostic or exposing a permission-mode Config.