Skip to content

skill.Frontmatter.AllowedTools does not follow the spec #1296

Description

@VDVsx

🔴 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:

  1. 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
  1. 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)
  1. 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?:

  • Always (100%)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions