🔴 Required Information
Describe the Bug:
skill.Frontmatter declares AllowedTools []string, so skill.Parse only accepts allowed-tools written as a YAML list. The Agent Skills specification, which frontmatter.go links to in its own doc comment, defines the field as a space-separated string:
| allowed-tools | No | Space-separated string of pre-approved tools the skill may use. (Experimental) |
The example below does not parse in ADK Go. The type accepts only a YAML list, which the specification never describes, and rejects the scalar form it does describe.
allowed-tools: Bash(git:*) Bash(jq:*) Read
For reference, ADK Python models the same field as a string and matches the spec(source):
allowed_tools: Optional[str] = Field(
None,
alias="allowed-tools",
serialization_alias="allowed-tools",
)
Steps to Reproduce:
- Create some test files with dummy skills inside a
skills dir
skills/spec-skill/SKILL.md - allowed-tools as the spec documents it: a space-separated string.
---
name: spec-skill
description: Uses the allowed-tools form the spec documents.
allowed-tools: Bash(git:*) Bash(jq:*) Read
---
Instructions.
SPEC
skills/list-skill/SKILL.md - allowed-tools as a YAML list, which the spec does not describe.
---
name: list-skill
description: Uses a YAML list, which the spec does not describe.
allowed-tools:
- Bash(git:*)
- Bash(jq:*)
- Read
---
Instructions.
LIST
skills/other-skill/SKILL.md - An unrelated, valid skill with no allowed-tools at all.
---
name: other-skill
description: An unrelated, perfectly valid skill.
---
Instructions.
OTHER
- Init and create a small program to load them:
main.go
package main
import (
"context"
"fmt"
"os"
"google.golang.org/adk/v2/tool/skilltoolset/skill"
)
func main() {
ctx := context.Background()
src := skill.NewFileSystemSource(os.DirFS("skills"))
for _, name := range []string{"spec-skill", "list-skill", "other-skill"} {
fm, err := src.LoadFrontmatter(ctx, name)
if err != nil {
fmt.Printf("LoadFrontmatter(%s) -> REJECTED: %v\n", name, err)
continue
}
fmt.Printf("LoadFrontmatter(%s) -> accepted: %q\n", name, fm.AllowedTools)
}
// One unloadable skill fails the whole listing, hiding the other two.
fms, err := src.ListFrontmatters(ctx)
fmt.Printf("\nListFrontmatters -> returned %d of 3 skills\n", len(fms))
fmt.Printf("ListFrontmatters -> err: %v\n", err)
- Run
go mod tidy && go run .
Expected Behavior:
allowed-tools: Bash(git:*) Bash(jq:*) Read parses, per the specification the package links to, and ListFrontmatters returns all three skills.
Observed Behavior:
The spec form is rejected, the form the spec does not describe is the only one accepted, and the two unrelated valid skills are lost along with it:
LoadFrontmatter(spec-skill) -> REJECTED: invalid frontmatter: parse frontmatter: parse frontmatter: yaml: unmarshal errors:
line 3: cannot unmarshal !!str `Bash(gi...` into []string
LoadFrontmatter(list-skill) -> accepted: ["Bash(git:*)" "Bash(jq:*)" "Read"]
LoadFrontmatter(other-skill) -> accepted: []
ListFrontmatters -> returned 0 of 3 skills
ListFrontmatters -> err: invalid frontmatter: parse frontmatter: parse frontmatter: yaml: unmarshal errors:
line 3: cannot unmarshal !!str `Bash(gi...` into []string
Environment Details:
- ADK Library Version: v2.2.0 (also reproduced on v2.1.0;
main carries the same declaration at the time of writing)
- OS: macOS
- Go Version: go1.26.5 darwin/arm64
Model Information:
Which model is being used: N/A — this is in frontmatter parsing, before any model is involved.
🟡 Optional Information
Regression:
No
Additional Context:
In the wild we see two main forms for this property, neither of which loads today:
allowed-tools: Bash(git:*) Bash(jq:*) Read — the spec's space-separated string.
allowed-tools: Read, Bash(kubectl get:*) — the comma-separated form Claude Code skills commonly use. Not in the spec, but common enough in existing skill files to be worth tolerating.
Note that a tool entry may itself contain spaces (Bash(git add:*)), so a plain strings.Fields split would break those; splitting on separators outside the parentheses handles both spellings.
A possible fix that keeps existing callers working: keep AllowedTools a []string for consumers, and give Frontmatter a custom UnmarshalYAML that accepts either a sequence or a scalar, splitting the scalar into items. That stays compatible with skills already written as a YAML list against the current behaviour, while accepting the spec form. If instead the field is changed to a plain string to match ADK Python exactly, I think that is a breaking change for anyone relying on the current list decoding.
Minimal Reproduction Code:
See the script under "Steps to Reproduce".
How often has this issue occurred?:
🔴 Required Information
Describe the Bug:
skill.FrontmatterdeclaresAllowedTools []string, soskill.Parseonly accepts allowed-tools written as a YAML list. The Agent Skills specification, which frontmatter.go links to in its own doc comment, defines the field as a space-separated string:The example below does not parse in ADK Go. The type accepts only a YAML list, which the specification never describes, and rejects the scalar form it does describe.
For reference, ADK Python models the same field as a string and matches the spec(source):
Steps to Reproduce:
skillsdirskills/spec-skill/SKILL.md - allowed-tools as the spec documents it: a space-separated string.
skills/list-skill/SKILL.md - allowed-tools as a YAML list, which the spec does not describe.
skills/other-skill/SKILL.md - An unrelated, valid skill with no allowed-tools at all.
main.go
go mod tidy && go run .Expected Behavior:
allowed-tools: Bash(git:*) Bash(jq:*) Readparses, per the specification the package links to, andListFrontmattersreturns all three skills.Observed Behavior:
The spec form is rejected, the form the spec does not describe is the only one accepted, and the two unrelated valid skills are lost along with it:
Environment Details:
maincarries the same declaration at the time of writing)Model Information:
Which model is being used: N/A — this is in frontmatter parsing, before any model is involved.
🟡 Optional Information
Regression:
No
Additional Context:
In the wild we see two main forms for this property, neither of which loads today:
allowed-tools: Bash(git:*) Bash(jq:*) Read— the spec's space-separated string.allowed-tools: Read, Bash(kubectl get:*)— the comma-separated form Claude Code skills commonly use. Not in the spec, but common enough in existing skill files to be worth tolerating.Note that a tool entry may itself contain spaces (
Bash(git add:*)), so a plainstrings.Fieldssplit would break those; splitting on separators outside the parentheses handles both spellings.A possible fix that keeps existing callers working: keep
AllowedToolsa[]stringfor consumers, and giveFrontmattera customUnmarshalYAMLthat accepts either a sequence or a scalar, splitting the scalar into items. That stays compatible with skills already written as a YAML list against the current behaviour, while accepting the spec form. If instead the field is changed to a plainstringto match ADK Python exactly, I think that is a breaking change for anyone relying on the current list decoding.Minimal Reproduction Code:
See the script under "Steps to Reproduce".
How often has this issue occurred?: