AlistGo/alist · error

failed to decode move response: %w

Error message

failed to decode move response: %w

What it means

The move response body failed to JSON-decode. Critically, unlike the rename path, executeMoveAPI never checks resp.StatusCode before decoding — so a 401/429/500 HTML or JSON error body is decoded as if it were the success envelope, and this decode error is what surfaces, masking the real status.

Source

Thrown at drivers/proton_drive/util.go:722

	}

	httpReq.Header.Set("Authorization", "Bearer "+d.credentials.AccessToken)
	httpReq.Header.Set("Accept", d.protonJson)
	httpReq.Header.Set("X-Pm-Appversion", d.webDriveAV)
	httpReq.Header.Set("X-Pm-Drive-Sdk-Version", d.sdkVersion)
	httpReq.Header.Set("X-Pm-Uid", d.credentials.UID)
	httpReq.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(httpReq)
	if err != nil {
		return fmt.Errorf("failed to execute move request: %w", err)
	}
	defer resp.Body.Close()

	var moveResp RenameResponse
	if err := json.NewDecoder(resp.Body).Decode(&moveResp); err != nil {
		return fmt.Errorf("failed to decode move response: %w", err)
	}

	if moveResp.Code != 1000 {
		return fmt.Errorf("move operation failed with code: %d", moveResp.Code)
	}

	return nil
}

func (d *ProtonDrive) DirectMove(ctx context.Context, srcObj model.Obj, dstDir model.Obj) (model.Obj, error) {
	//fmt.Printf("DEBUG DirectMove: srcPath=%s, dstPath=%s", srcObj.GetPath(), dstDir.GetPath())

	srcLink, err := d.searchByPath(ctx, srcObj.GetPath(), srcObj.IsDir())
	if err != nil {
		return nil, fmt.Errorf("failed to find source: %w", err)
	}

	var dstParentLinkID string

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Add a status check before decoding (mirror executeRenameAPI) so the true status is reported
  2. Log the raw body on decode failure
  3. Fix network interception if HTML bodies appear

Example fix

// before
var moveResp RenameResponse
if err := json.NewDecoder(resp.Body).Decode(&moveResp); err != nil {
// after
if resp.StatusCode != http.StatusOK {
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
	return fmt.Errorf("move failed with status %d: %s", resp.StatusCode, body)
}
var moveResp RenameResponse
if err := json.NewDecoder(resp.Body).Decode(&moveResp); err != nil {
Defensive patterns

Strategy: try-catch

Try / catch

var syn *json.SyntaxError
var un *json.UnmarshalTypeError
if errors.As(err, &syn) || errors.As(err, &un) {
    // decode failure may actually be a masked non-200 (no status check in driver):
// check token freshness and network path, then retry once
}

Prevention

When it happens

Trigger: Any non-200 response whose body is HTML (proxy/Cloudflare) or an error JSON that does not fit RenameResponse; empty body from gateway errors; truncated body on flaky links.

Common situations: Expired token producing a 401 error JSON that the struct cannot absorb; middleboxes answering with HTML; backend 5xx pages — all misreported as 'failed to decode move response'.

Understand the failure class

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/9df77a4e8a0f5aae. Report an issue: GitHub.