Skip to content

fix(agents): keep named task allows winning over the ambient wildcard deny - #787

Merged
ualtinok merged 1 commit into
cortexkit:masterfrom
DreamchaserLG:fix/task-permission-wildcard-named-allows
Oct 26, 2026
Merged

ualtinok merged 1 commit into
cortexkit:masterfrom
DreamchaserLG:fix/task-permission-wildcard-named-allows

Conversation

@DreamchaserLG

@DreamchaserLG DreamchaserLG commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Approved issue: #777 — confirmed by the maintainer in the issue thread; this implements exactly the direction from that reply so the queued fix can land sooner.

Summary

denyTaskRoutingToAgents() moved a literal "*": "deny" entry from the agent's own permission.task map to the end of the merged map whenever one was present. OpenCode resolves task permissions with the last matching rule, so for the deny-by-default-with-exceptions shape

permission:
  task:
    "*": deny
    my-scout: allow

the re-appended wildcard overrode every named allow and the Task tool disappeared from the agent entirely — it could no longer dispatch the very subagent it named (#777).

The wildcard is now moved only when its sole companions are our own appended agent-id denies, so:

  • task: { "*": "deny" } alone — unchanged: the wildcard is re-emitted last and the Task tool stays hidden.
  • task: { "*": "deny", "x": "allow" } — the agent's own ordering is kept; the named allow still wins, the tool remains visible, and every other target is still denied by the user's own wildcard.

One deliberate refinement over the predicate sketched in the issue (Object.keys(configuredTask).every(id => id === "*")): internal agent ids are exempted from the "sole companion" check. Our own merged output is exactly internal denies + wildcard, so the strict predicate breaks the existing re-registration idempotence contract (Task wildcard re-emission is idempotent across repeated registration); exempting our own ids keeps that test green while still never overriding a user-authored rule.

Harness coverage (required)

For each entry select: covered / not applicable — surface does not exist there / NOT covered.

  • OpenCode 1: covered — denyTaskRoutingToAgents() runs in the plugin's config hook (packages/plugin/src/index.ts); the new unit tests assert both the merged map and the evaluator-visible outcome via permissionDisabled().
  • OpenCode 2: not applicable — surface does not exist there: the v2 server registers agents through rule-based hiddenChildPermissions (packages/plugin/src/v2/hooks/hidden-child.ts) and never calls the merged task map.
  • Pi: not applicable — the Pi plugin has its own permission module; this function has no call site in the Pi path.
  • OMP: not applicable — no call site outside the OpenCode plugin config hook.
  • Rust module: not applicable — pure TypeScript config shaping.

How verified

  • bun install --frozen-lockfile — 1108 packages, clean install.
  • bun test src/agents/permissions.test.ts src/agent-registration-drift.test.ts → 52 pass / 0 fail (includes the 2 new regression tests).
  • bun test src/agents/permissions.test.ts src/agent-registration-drift.test.ts src/index-hook-delegation.test.ts src/index-refresh.test.ts src/standalone-plugin-marker.test.ts src/standalone-plugin-marker-entry.test.ts → 63 pass / 0 fail.
  • Negative control: with only the permissions.ts change reverted (git stash push -- packages/plugin/src/agents/permissions.ts), the new test named task allows survive an ambient wildcard deny (#777) fails on the pre-fix code — the regression test pins the bug.
  • biome check src/agents/permissions.ts src/agent-registration-drift.test.ts → clean.
  • tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit (the repo's typecheck order) → clean.
  • Environment note: packages/pi-plugin tests crash under the locally available bun 1.2.23 (bun runtime crash, bun.report link emitted). Reproduced identically on a clean checkout with no local changes, so it is a local toolchain floor (the repo pins bun@1.4.2), not an effect of this change; the packages/plugin suite used above does not hit it.

… deny

denyTaskRoutingToAgents() moved a literal '*': 'deny' entry from the
agent's own permission.task map to the end of the merged map whenever
one was present. OpenCode resolves task permissions with the last
matching rule, so with a deny-by-default-with-exceptions map like

    task: { "*": "deny", "my-scout": "allow" }

the re-appended wildcard overrode every named allow and the Task tool
disappeared from the agent entirely - it could not dispatch the very
subagent it named.

Only move the wildcard when its sole companions are our own appended
agent-id denies, so a lone catch-all still hides the tool while a mixed
map keeps the agent's own ordering. Exempting our internal ids from
that check keeps re-merging our own output idempotent. Add regression
tests covering the mixed map's evaluated outcome via
permissionDisabled() and the re-merge idempotence of the mixed shape.

Fixes cortexkit#777

Co-authored-by: ZCode Scout <noreply@zcode.local>
Signed-off-by: NefelibateLG <3453510037@qq.com>
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

#777 has the design-approved label; this PR can be reviewed.

@ualtinok
ualtinok merged commit e9aa269 into cortexkit:master Oct 26, 2026
9 of 10 checks passed
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.

2 participants