Files
goldbrain/.source-references/references-only/reference vault/memory/decisions/2026-05-28-file-obsidian-heading-resolver-bug.md
T
2026-06-04 16:20:56 -05:00

6.4 KiB

type, date, status, tags, related
type date status tags related
decision 2026-05-28 open
obsidian
rest-api
bug-report
upstream
obsidian-memory-plugin

2026-05-28 — File upstream issue for Obsidian Local REST API heading resolver bug

Decision

Draft and file a bug report at coddingtonbear/obsidian-local-rest-api for the heading-resolver failure that affects both Target-Type: heading PATCH and GET /heading/... reads.

Why

  • Bug persists across a major plugin version bump (3.6 → 4.1.1), so it's not a regression in a transient build — it's a real defect that's been sitting in the resolver for at least one release.
  • The bug silently degrades the obsidian-memory skill: every documented PATCH-heading example in the skill's references/api-reference.md fails, forcing fallbacks to POST/PUT/frontmatter-PATCH. We have working workarounds, but they preclude surgical section edits that the skill instructs Claude to do.
  • Reproducer is small (4 curl calls) and deterministic — high signal-to-noise for a maintainer.
  • No alternative — only the upstream maintainer can fix it. Local workarounds will live in obsidian-memory-plugin indefinitely until this lands.

How to apply

  • The full draft (title, repro, variations tried, ruled-out causes, suspected cause) is at D:\REMOTE CODING\unifi-access-dashboard\obsidian-rest-api-issue-draft.md on the dashboard machine.
  • Before filing: fill in two placeholders — Obsidian app version and vault filesystem.
  • After filing: come back to this decision and update status: from openfiled with the issue URL; if/when fixed, mark resolved and remove the workaround section from obsidian-memory-plugin.

Context that won't be in the issue body

  • All testing was done against the vault at https://10.2.0.2:27124 from Claude Code sessions over the LAN.
  • The skill's documented PATCH-heading example (in references/api-reference.md) references a key that has a typo (8c instead of e7 in one digit) — unrelated to this bug but worth noting if anyone tries to copy-paste from there.
  • Target-Type: block also fails with invalid-target. Probably the same resolver but not verified — left out of the issue body to keep the report tight; can add as a follow-up comment if relevant.

Open

  • Fill in placeholders in the draft and post the issue
  • Paste the issue URL back here under "How to apply"
  • When fixed upstream: drop the workaround section from obsidian-memory-plugin and switch the skill back to PATCH-heading where it makes sense

Draft body (copy-paste ready)

See obsidian-rest-api-issue-draft.md on the dashboard machine, or mirrored below:


Heading resolver broken: Target-Type: heading PATCH and GET /heading/... both fail on any valid heading (4.1.1)

Summary

In Obsidian Local REST API 4.1.1, two endpoints that both rely on heading resolution always fail, even on a freshly written file with trivially-valid markdown headings:

  • PATCH /vault/<path> with Target-Type: heading → HTTP 400 invalid-target
  • GET /vault/<path>/heading/<heading-text> → HTTP 404 Not Found

Target-Type: frontmatter PATCH on the same file works fine, so the PATCH route itself is healthy. The bug is isolated to the heading-resolution code path shared by these two endpoints.

Also reproduced in 3.6 before upgrading; the 3.6 → 4.1.1 upgrade did not change the behavior.

Minimal reproducer

HOST="https://YOUR-HOST:27124"
KEY="YOUR_API_KEY"

# 1. PUT a fresh, simple file (HTTP 204 expected)
cat > /tmp/repro.md <<'EOF'
---
type: scratch
---

# Hello

## Section A

Content under A.

## Section B

Content under B.
EOF

curl -sk -w "PUT: %{http_code}\n" -X PUT \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: text/markdown" \
  --data-binary @/tmp/repro.md \
  "$HOST/vault/scratch/repro.md"

# 2. PATCH append under heading "Section A" — expected 200, actual 400 invalid-target
echo "appended" > /tmp/p.md
curl -sk -w "\nPATCH heading: %{http_code}\n" -X PATCH \
  -H "Authorization: Bearer $KEY" \
  -H "Operation: append" \
  -H "Target-Type: heading" \
  -H "Target: Section A" \
  -H "Content-Type: text/markdown" \
  --data-binary @/tmp/p.md \
  "$HOST/vault/scratch/repro.md"

# 3. GET the heading's content via URL path — expected 200, actual 404
curl -sk -w "\nGET heading: %{http_code}\n" \
  -H "Authorization: Bearer $KEY" \
  "$HOST/vault/scratch/repro.md/heading/Section%20A"

# 4. CONTROL: PATCH frontmatter — works (HTTP 200)
curl -sk -w "\nPATCH frontmatter: %{http_code}\n" -X PATCH \
  -H "Authorization: Bearer $KEY" \
  -H "Operation: replace" \
  -H "Target-Type: frontmatter" \
  -H "Target: type" \
  -H "Content-Type: application/json" \
  --data '"updated"' \
  "$HOST/vault/scratch/repro.md"

Actual output

PUT: 204
{"message":"...invalid-target","errorCode":40080}  PATCH heading: 400
{"message":"Not Found","errorCode":40400}          GET heading: 404
PATCH frontmatter: 200

Variations tried (all fail identically with invalid-target / 404)

  • Single-word heading (Target: Section) and multi-word (Target: Section A)
  • URL-encoded header value (Target: Section%20A)
  • Operation: append, prepend, and replace
  • Both freshly-PUT files (LF endings) and files originally written by Obsidian
  • Target-Type: block also returns invalid-target

What I ruled out

  • CRLF / BOM in source file — fresh file was written by curl --data-binary with LF-only content
  • Spaces in heading text — single-word headings fail the same way
  • URL encoding of Target: header value — raw and percent-encoded both fail
  • PATCH route generally broken — Target-Type: frontmatter succeeds on the same file
  • Version regression in 4.1.1 — same behavior in 3.6 before upgrading

Environment

  • Obsidian Local REST API: 4.1.1 (also reproduced in 3.6)
  • Obsidian: <fill in>
  • OS hosting Obsidian: <fill in>
  • Client: curl over HTTPS to :27124 with -k (self-signed cert)
  • Vault filesystem: <fill in>

Suspected cause

Since GET /vault/<path>/heading/<name> and Target-Type: heading PATCH fail in lockstep (and frontmatter resolution works fine on the same request path), the most likely location is whichever helper enumerates headings within a parsed file's AST — perhaps it's getting an empty list, or matching on the wrong field.

Happy to bisect or add logging if you can point me at the relevant file.