tailscale/tailscale · error
unmarshal raw grants %s: %v
Error message
unmarshal raw grants %s: %v
What it means
The drive capability values from the ACL are passed to drive.ParsePermissions, which json.Unmarshals each raw grant into {shares: []string, access: string} (drive/remote_permissions.go:42). A value that is not valid JSON for that shape fails, and the raw error 'unmarshal raw grants <raw>: <detail>' becomes the HTTP 500 body of the peerapi drive endpoint (peerapi_drive.go:52 serves err.Error()).
Source
Thrown at ipn/ipnlocal/peerapi_drive.go:52
}
capsMap := h.PeerCaps()
driveCaps, ok := capsMap[peercap.Taildrive]
if !ok {
h.logf("taildrive: not permitted")
http.Error(w, "taildrive not permitted", http.StatusForbidden)
return
}
rawPerms := make([][]byte, 0, len(driveCaps))
for _, cap := range driveCaps {
rawPerms = append(rawPerms, []byte(cap))
}
p, err := drive.ParsePermissions(rawPerms)
if err != nil {
h.logf("taildrive: error parsing permissions: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fs, ok := h.ps.b.sys.DriveForRemote.GetOK()
if !ok {
h.logf("taildrive: not supported on platform")
http.Error(w, "taildrive not supported on platform", http.StatusNotFound)
return
}
wr := &httpResponseWrapper{
ResponseWriter: w,
}
bw := &requestBodyWrapper{
ReadCloser: r.Body,
}
r.Body = bw
defer func() {View on GitHub (pinned to 6e0912f979)
Solutions
- Inspect the drive capability values in the tailnet policy file.
- Rewrite each entry as an object: {"shares": ["*"], "access": "rw"} where access is 'ro' or 'rw'.
- Apply the corrected policy and retry; the error clears once every grant parses.
Example fix
// before: plain string instead of grant objects
"app": {"tailscale.com/cap/drive": ["rw"]}
// after: grant objects with shares and access
"app": {"tailscale.com/cap/drive": [{"shares": ["*"], "access": "rw"}]} Defensive patterns
Strategy: validation
Validate before calling
import "encoding/json"
type driveGrant struct {
Shares []string `json:"shares"`
Access string `json:"access"`
}
// Lint drive grants before applying the policy.
func validDriveGrants(raw []json.RawMessage) error {
for _, r := range raw {
var g driveGrant
if err := json.Unmarshal(r, &g); err != nil {
return fmt.Errorf("bad drive grant %s: %w", r, err)
}
if g.Access != "ro" && g.Access != "rw" {
return fmt.Errorf("access must be ro or rw, got %q", g.Access)
}
}
return nil
} Prevention
- Validate policy JSON with a lint step before applying.
- Use the admin editor's autocomplete instead of hand-writing capability arrays.
- Add a CI check that unmarshals every app capability against its expected shape.
When it happens
Trigger: A policy grant whose app capability for drive is a plain string or number instead of an object, e.g. "tailscale.com/cap/drive": ["rw"], or an object with wrong-typed fields such as shares not being an array.
Common situations: Hand-edited ACL JSON; tooling writing a legacy capability format; copy-paste from documentation of a different version.
Related errors
- json.Unmarshal %q: %w
- could not parse serve config: %w
- taildrive not permitted
- invalid dns.OSConfig: %w
- invalid query response: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/ae51a8a91ad78b23.
Report an issue: GitHub.