What happened
When running taskctl start <issueNumber>, the Composer agent is spawned to decompose a GitHub issue into a task graph. The agent attempts to read source files for context but gets permission denied for all tools. It falls back to decomposing based solely on the issue text, producing poor output with no codebase awareness.
Observed error:
I am encountering permission issues reading the source files directly. Based on the issue description, I have enough context from the issue itself to decompose this properly.
The Composer then returns invalid JSON because it cannot understand the codebase structure.
Expected behaviour
The Composer agent should be able to:
- Read source files to understand relevant code locations
- Search the codebase to find related modules and files
- Produce accurate task decompositions with correct file paths and module labels
Root cause
In packages/opencode/src/agent/agent.ts (lines 204-216), the composer agent is defined with:
"composer": {
permission: PermissionNext.merge(
defaults,
PermissionNext.fromConfig({
"*": "deny", // Denies ALL tools
}),
user,
),
}
The "*": "deny" rule overrides all default allows via last-match-wins evaluation (evaluate() iterates backwards in the merged ruleset). Since there are no explicit tool allows for the composer, disabled() hides every tool from the agent.
This contrasts with the explore agent (lines 132-157) which correctly uses "*": "deny" paired with explicit allows:
"explore": {
permission: PermissionNext.merge(
defaults,
PermissionNext.fromConfig({
"*": "deny",
grep: "allow", glob: "allow", list: "allow",
bash: "allow", webfetch: "allow", websearch: "allow",
codesearch: "allow", read: "allow",
}),
user,
),
}
Additionally, Session.createNext in composer.ts:36-41 passes permission: [] (empty array), which does not override or supplement the agent-level permissions — the runtime merge is PermissionNext.merge(taskAgent.permission, session.permission ?? []).
Why this matters
Without codebase access, the Composer cannot:
- Identify which files need modification
- Validate that module/file labels reference real paths
- Detect existing utilities that could be reused
- Produce dependency-accurate task graphs
It operates blind, producing generic decompositions that miss critical codebase-specific context.
Proposed solution
Add explicit read-only tool allows to the composer agent definition, following the explore agent pattern. The allowed tool set should be extracted into a named constant for easy adjustment:
// In agent.ts — reusable read-only toolset for codebase-aware agents
const READONLY_TOOLS = {
"*": "deny",
read: "allow",
grep: "allow",
glob: "allow",
list: "allow",
codesearch: "allow",
bash: "allow", // for gh, vipune, colgrep
external_directory: { [Truncate.GLOB]: "allow" },
} satisfies Config.Permission
Both composer and steering agents (which currently have the same "*": "deny" with no allows) should use this or a similar toolset. This makes it trivial to adjust permissions for any codebase-aware read-only agent in one place.
Acceptance Criteria
Quality Gates
What happened
When running
taskctl start <issueNumber>, the Composer agent is spawned to decompose a GitHub issue into a task graph. The agent attempts to read source files for context but gets permission denied for all tools. It falls back to decomposing based solely on the issue text, producing poor output with no codebase awareness.Observed error:
The Composer then returns invalid JSON because it cannot understand the codebase structure.
Expected behaviour
The Composer agent should be able to:
Root cause
In
packages/opencode/src/agent/agent.ts(lines 204-216), the composer agent is defined with:The
"*": "deny"rule overrides all default allows via last-match-wins evaluation (evaluate()iterates backwards in the merged ruleset). Since there are no explicit tool allows for the composer,disabled()hides every tool from the agent.This contrasts with the
exploreagent (lines 132-157) which correctly uses"*": "deny"paired with explicit allows:Additionally,
Session.createNextincomposer.ts:36-41passespermission: [](empty array), which does not override or supplement the agent-level permissions — the runtime merge isPermissionNext.merge(taskAgent.permission, session.permission ?? []).Why this matters
Without codebase access, the Composer cannot:
It operates blind, producing generic decompositions that miss critical codebase-specific context.
Proposed solution
Add explicit read-only tool allows to the composer agent definition, following the
exploreagent pattern. The allowed tool set should be extracted into a named constant for easy adjustment:Both
composerandsteeringagents (which currently have the same"*": "deny"with no allows) should use this or a similar toolset. This makes it trivial to adjust permissions for any codebase-aware read-only agent in one place.Acceptance Criteria
taskctl starttaskctl startproduces valid JSON decomposition with real file/module references"*": "deny"with no allows)Quality Gates
bun run typecheck && bun testpass