tailscale/tailscale · error

%s: %s

Error message

%s: %s

What it means

Returned by Client.PushFile (client/local/local.go:877) when PUTting a file to /localapi/v0/files/<target> does not return 200. The status and full body are combined into an error passed through bestError(), which prefers a structured message from the body when one exists. A 200 with an error body is handled elsewhere (bestError path after copying), so this line is strictly the non-OK status case.

Source

Thrown at client/local/local.go:877

// The name parameter is the original filename, not escaped.
func (lc *Client) PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error {
	req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+apitype.LocalAPIHost+"/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
	if err != nil {
		return err
	}
	if size != -1 {
		req.ContentLength = size
	}
	res, err := lc.doLocalRequestNiceError(req)
	if err != nil {
		return err
	}
	if res.StatusCode == 200 {
		io.Copy(io.Discard, res.Body)
		return nil
	}
	all, _ := io.ReadAll(res.Body)
	return bestError(fmt.Errorf("%s: %s", res.Status, all), all)
}

// CheckIPForwarding asks the local Tailscale daemon whether it looks like the
// machine is properly configured to forward IP packets as a subnet router
// or exit node.
//
// API maturity: this method is not considered a stable API and is
// subject to change between releases.
func (lc *Client) CheckIPForwarding(ctx context.Context) error {
	if !buildfeatures.HasAdvertiseRoutes {
		return nil
	}
	body, err := lc.get200(ctx, "/localapi/v0/check-ip-forwarding")
	if err != nil {
		return err
	}
	var jres struct {
		Warning string

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Read the body in the error — it states whether it is policy, target validity, or state
  2. Verify the target with FileTargets and confirm it is online and file-capable
  3. Enable Taildrop/file receiving for the target (admin policy + node settings) before pushing
  4. Retry once after re-fetching targets if the failure looks transient
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the target accepts files before pushing.
fts, err := lc.FileTargets(ctx)
if err != nil {
	return err
}
ok := false
for _, ft := range fts {
	if ft.Node.Key == targetKey {
		ok = true
		break
	}
}
if !ok {
	return fmt.Errorf("target %x not file-capable or offline", targetKey)
}

Try / catch

if err := lc.PushFile(ctx, target, size, r); err != nil {
	if strings.Contains(err.Error(), "not enabled") || strings.Contains(err.Error(), "denied") {
		return errors.New("Taildrop disabled by policy or target settings")
	}
	return err // bestError already prefers the structured daemon message
}

Prevention

When it happens

Trigger: Pushing to a target that is not a valid peer (unknown key/IP); Taildrop file sending disabled by policy on the tailnet or for the target node; the target node offline or not accepting files; permission denial on the local API.

Common situations: Sending files to devices that have file receiving off; policy changes disabling Taildrop mid-integration; stale FileTargets results used after a peer went away.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/a04ca8106307f370. Report an issue: GitHub.