Skip to content

fix: URL-encode Target header for non-ASCII heading names#69

Open
marcoaperez wants to merge 1 commit intojacksteamdev:mainfrom
marcoaperez:fix/encode-patch-target-header
Open

fix: URL-encode Target header for non-ASCII heading names#69
marcoaperez wants to merge 1 commit intojacksteamdev:mainfrom
marcoaperez:fix/encode-patch-target-header

Conversation

@marcoaperez
Copy link
Copy Markdown

@marcoaperez marcoaperez commented Feb 26, 2026

Summary

Two fixes for the PATCH endpoint in patch_vault_file and patch_active_file:

1. URL-encode Target header for non-ASCII heading names

patch_vault_file and patch_active_file fail with Header 'Target' has invalid value when the target heading contains non-ASCII characters (e.g., accented letters like ó, é, á, or special characters like ).

This is a common scenario for users writing in Spanish, French, German, and other languages that use diacritics.

Example errors

Header 'Target' has invalid value: 'Información del host'
Header 'Target' has invalid value: 'Diario de acciones – BenditaRespaldo'
Header 'Target' has invalid value: 'Decisiones y convenciones::Método de alta'

Root cause

HTTP headers only accept ASCII characters. The Target header value was passed as-is without encoding. The Local REST API OpenAPI spec explicitly states:

Target to patch; this value can be URL-Encoded and must be URL-Encoded if it includes non-ASCII characters.

(See packages/obsidian-plugin/docs/openapi.yaml, lines 361-362)

2. Prevent replace operation from duplicating headings

When using replace on a heading, content was duplicated instead of replaced. A new heading was created at the end of the file with the new content, while the original heading and its content remained untouched.

Root cause

The Create-Target-If-Missing header was always sent as true, even for replace operations. When the API couldn't find the target heading (e.g., when using a leaf heading name instead of the full path), instead of returning an error, it silently created a new duplicate heading at the end of the file.

For replace, if the target doesn't exist, the operation should fail — you can't replace something that doesn't exist.

Changes

packages/mcp-server/src/features/local-rest-api/index.ts

  • Apply encodeURIComponent() to the Target header in both patch_active_file and patch_vault_file
  • Apply the same encoding to the Target-Delimiter header
  • Only send Create-Target-If-Missing: true for append/prepend operations, not for replace

packages/shared/src/types/plugin-local-rest-api.ts

  • Improve target parameter description to emphasize that headings must use the full path delimited by :: (e.g. Heading 1::Subheading 1:1)

packages/shared/src/types/plugin-templater.ts

  • Change value imports from "obsidian" to type-only imports (import type { ... })
  • All imported symbols are only used in type positions, so they don't need runtime imports

Test plan

  • Verified patch_vault_file works with headings containing accented characters (e.g., Información del host)
  • Verified replace with full heading path works correctly (content replaced, no duplication)
  • Verified replace with leaf-only heading name now returns invalid-target error instead of duplicating
  • Verified append with Create-Target-If-Missing still creates headings when missing (no regression)
  • TypeScript check passes on both shared and mcp-server packages
  • No changes to existing behavior for ASCII-only headings

🤖 Generated with Claude Code

The Local REST API OpenAPI spec states that the Target header value
"can be URL-Encoded and *must* be URL-Encoded if it includes
non-ASCII characters." However, patch_vault_file and patch_active_file
were passing the Target header value as-is, causing HTTP header
validation errors for any heading containing accented characters
(e.g., "Información", "Método") or special characters (e.g., em-dash).

This commit:
- Applies encodeURIComponent() to the Target header in both
  patch_active_file and patch_vault_file
- Applies the same encoding to Target-Delimiter header
- Changes obsidian imports in plugin-templater.ts to type-only imports,
  since they are only used in type positions (interfaces/type aliases)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@netlify
Copy link
Copy Markdown

netlify bot commented Feb 26, 2026

Deploy Preview for superb-starlight-b5acb5 canceled.

Name Link
🔨 Latest commit cadfc41
🔍 Latest deploy log https://app.netlify.com/projects/superb-starlight-b5acb5/deploys/69a0c830c7966f000850a722

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant