siyuan-note/siyuan · error

parse [h] failed

Error message

parse [h] failed: %s

What it means

Error from parseForwardProxyParams in the network forward-proxy endpoint: the base64url-decoded [h] query parameter cannot be unmarshaled into a map[string][]string HTTP header record, so the client sent a malformed header encoding for the proxied request.

Solutions

  1. Serialize headers as a JSON object whose values are string arrays: {"Header": ["value"]}
  2. Run the object through JSON.stringify (or json.Marshal) before base64-encoding
  3. Validate the JSON parses into map[string][]string in your client before sending

Example fix

// before
JSON.stringify({"X-Token": "abc"}) // wrong shape, values must be arrays
// after
JSON.stringify({"X-Token": ["abc"]})
Defensive patterns

Strategy: validation

Validate before calling

const record = {};
for (const [k, v] of Object.entries(headers)) {
  record[k] = Array.isArray(v) ? v : [v];
}
const h = toBase64Url(JSON.stringify(record));

Type guard

function isHeaderRecord(o) {
  return typeof o === "object" && o !== null && Object.values(o).every(v => Array.isArray(v) && v.every(x => typeof x === "string"));
}

Prevention

When it happens

Trigger: `h` decodes to text that is not JSON (plain text, single-quoted JSON, a flat map[string]string instead of map[string][]string).

Common situations: Clients serializing headers as {"X-Token":"abc"} instead of {"X-Token":["abc"]}; string-concatenated pseudo-JSON; forgetting JSON.stringify before base64-encoding.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/af9a3a77e8f3f67d. Report an issue: GitHub.

Appendix: source

Thrown at kernel/api/network.go:363

	}
	parsedURL, err = url.ParseRequestURI(string(uBytes))
	if err != nil {
		err = fmt.Errorf("parse [u] failed: %s", err.Error())
		return
	}

	h := http.Header{}
	headers = &h
	hParam := c.Query("h")
	if hParam != "" {
		hBytes, decErr := base64.RawURLEncoding.DecodeString(hParam)
		if decErr != nil {
			err = fmt.Errorf("decode [h] failed: %s", decErr.Error())
			return
		}
		var record map[string][]string
		if jsonErr := json.Unmarshal(hBytes, &record); jsonErr != nil {
			err = fmt.Errorf("parse [h] failed: %s", jsonErr.Error())
			return
		}

		for k, vs := range record {
			for _, v := range vs {
				h.Add(k, v)
			}
		}
	}

	timeout = 30 * time.Second
	tParam := c.Query("t")
	if tParam != "" {
		if t, parseErr := time.ParseDuration(tParam); parseErr != nil {
			err = fmt.Errorf("parse [t] failed: %s", parseErr.Error())
			return
		} else {
			timeout = t

View on GitHub (pinned to 9f775e8a12)