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

  1. Inspect the drive capability values in the tailnet policy file.
  2. Rewrite each entry as an object: {"shares": ["*"], "access": "rw"} where access is 'ro' or 'rw'.
  3. 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

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


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/ae51a8a91ad78b23. Report an issue: GitHub.