Neorg Tangle Multiple Files: Fix Syntax & Concurrency
Solve Neorg tangle multiple files issues: restore syntax highlighting with Treesitter parsers like conf/sh and fix concurrency failures by setting core.tangle concurrency=1 for reliable literate programming exports without retries.
How to tangle multiple files from a single Neorg file with correct syntax highlighting and without concurrency issues?
I’m consolidating many system configuration files into one Neorg file using metadata for tangling:
@document.meta
tangle: {
tmux: ~/.tmux.conf
kitty: ~/.config/kitty/kitty.conf
...
}
@end
@code tmux
set -g prefix щ
bind-key щ send-prefix
unbind C-b
...
@end
@code kitty
# vim:fileencoding=utf-8:ft=conf:foldmethod=marker
#: Fonts {{{
#: kitty has very powerful font management. You can configure
#: individual font faces and even specify special fonts for particular
#: characters.
font_family monospace
bold_font auto
italic_font auto
bold_italic_font auto
confirm_os_window_close 0
...
@end
This approach tangles all 37 files correctly most of the time, but I face two issues:
- Syntax highlighting loss: Using custom language names (e.g.,
tmux,kitty) for blocks disables highlighting, as different files may share the same language. - Concurrency failures: Tangling occasionally fails (likely due to concurrency), requiring multiple retries until all files succeed without errors.
What I’ve tried:
- Directives like
#{ :xdefaults: }or#tangle /some/path/init.rbfollowed by@code rubyblocks, but they often error or fail to tangle. - Absolute paths cause issues due to a known treesitter parser bug in Norg, which recommends metadata (currently used but feels ad-hoc and unreliable).
How can I restore proper syntax highlighting for custom language blocks and ensure reliable tangling without concurrency problems or retries?
To tangle multiple files from a single Neorg file reliably, swap custom language names like tmux or kitty for standard Treesitter parsers such as @code conf or @code sh—this restores syntax highlighting instantly without losing it across your 37 blocks. For concurrency issues causing sporadic failures, configure core.tangle with concurrency = 1 to serialize writes, eliminating race conditions and retries. Your metadata approach stays solid; just tweak the keys to match these real language IDs for seamless neorg tangle multiple files output every time.
Contents
- Neorg Tangling Basics
- Restoring Syntax Highlighting
- Fixing Concurrency Failures
- Updated Metadata Configuration
- Advanced Tips and Troubleshooting
- Sources
- Conclusion
Neorg Tangling Basics
Neorg’s tangling extracts code blocks from your literate notes into actual files—like turning one massive .norg doc into 37 configs for tmux, kitty, and beyond. You’ve nailed the metadata setup, which overrides per-block paths cleanly and dodges those Treesitter parser glitches with directives like #{ :xdefaults: }. But why the hiccups? Custom langs like @code tmux confuse Neorg’s highlighter since there’s no dedicated Treesitter parser for them. It falls back to plain text, killing colors and indent guides.
The official Neorg wiki on tangling spells it out: Neorg relies on the lang name in @code to pick a parser. No match? No shine. And with async tangling firing off 37 jobs at once, file locks clash—especially on dotfiles in ~/.config. Your retries work around it, but that’s no way to live. Good news: fixes are straightforward config tweaks away.
Think of it this way. You’re literate programming your entire Neovim setup. Why settle for half-baked highlights or flaky exports when a few swaps make it pro-grade?
Restoring Syntax Highlighting
Highlighting vanishes because tmux and kitty aren’t Treesitter langs—they’re configs. Swap to @code conf for kitty’s kitty.conf (it parses generic conf files perfectly), @code sh for tmux (shell syntax nails those binds), or whatever matches your targets. Here’s your tmux block reborn:
@code sh
set -g prefix щ
bind-key щ send-prefix
unbind C-b
# ... rest of your config
@end
And kitty? Straight @code conf:
@code conf
# vim:fileencoding=utf-8:ft=conf:foldmethod=marker
font_family monospace
bold_font auto
# ... your font wizardry
@end
Boom—full syntax, folds, even semantic tokens if your Treesitter’s fresh. The Neorg syntax wiki backs this: use the “actual file-type name” to trigger parsers. Kitty and tmux share conf parsers in Neovim land, so no conflicts across blocks.
Tried directives like #tangle /path? They flake on parser bugs, as you saw. Metadata wins here, but only if keys align with these real langs. Pro tip: Toss a Vim modeline at the block top, like your kitty example. Neorg respects it for extra filetype hints, ensuring even edge cases glow.
What about truly custom stuff without parsers? Rare, but Stack Overflow users suggest autocommands:
vim.api.nvim_create_autocmd("FileType", {
pattern = "norg",
callback = function()
if vim.fn.getline(1):match("^@code custom:tmux") then
vim.bo.filetype = "tmux"
end
end,
})
Overkill for most, though. Stick to conf/sh—it just works.
Fixing Concurrency Failures
Async tangling’s great for speed… until 37 files hammer your disk. Neorg queues them parallel, but I/O races mean partial writes or EAGAIN errors. Retries mask it, but set concurrency = 1 to serialize everything. Add this to your Neorg setup:
require('neorg').setup({
['core.defaults'] = {},
['core.tangle'] = {
config = {
concurrency = 1, -- Serial writes, no races
},
},
-- ... other modules
})
The tangling wiki calls this out explicitly for “many files.” Stack Overflow echoes it as tangle_concurrency = 1 (same effect). Run :Neorg tangle current-file manually to test—no more flakes, even on SSDs under load.
Why does this bite? Neorg’s jobs don’t retry by default; they bail on contention. Serial mode queues 'em safe. For huge docs, pair with core.syntax’s chunk_size = 1000 to keep editing snappy.
Updated Metadata Configuration
Your meta block’s close—just remap to real langs:
@document.meta
tangle: {
sh: ~/.tmux.conf
conf: ~/.config/kitty/kitty.conf
# lua: ~/.config/nvim/init.lua
# ... map all 37
}
@end
Neorg funnels @code sh blocks to tmux.conf, conf to kitty.conf. Override per-block with :tangle /custom/path if needed, but metadata scales better. GitHub issue #957 notes directory tangling’s WIP, so single-file multi-output like this rules for now.
Save, :w, and if auto_tangle_on_write is on? Files update live. Disable it for edits (tangle_on_write = false) to avoid mid-write surprises.
Advanced Tips and Troubleshooting
Hitting parser snags still? Update Treesitter: :TSUpdate. Neorg v0.0. something? Grab latest—bugs like absolute path fails got patched.
For literate Neorg configs ala Org-Babel, Reddit folks dream of autosync, but tangle’s close enough. Hook :Neorg tangle to a leader key for one-shot exports.
Edge case: Mixed langs in one file? Metadata handles it; just unique keys. Monitor with :Neorg log for write fails.
| Issue | Quick Fix |
|---|---|
| No highlight | @code conf not kitty |
| Races | concurrency = 1 |
| Parser bug | Metadata > directives |
| Slow on 37 files | chunk_size = 1000 in syntax |
Test loop: Tangle, check :e ~/.tmux.conf for highlights, repeat. Solid.
Sources
- Tangling - nvim-neorg/neorg Wiki
- How to tangle many files from neorg with correct syntax highlighting and without concurrency issues? - Stack Overflow
- Syntax - nvim-neorg/neorg Wiki
- tangle entire directory of files - Issue #957 - nvim-neorg/neorg
- r/neovim: Literate Neorg configuration
Conclusion
Neorg tangle multiple files shines with standard langs like @code conf for syntax highlighting and concurrency = 1 to crush those failures—no more retries on your 37-file beast. Tweak metadata keys, restart Neorg, and your literate config empire runs smooth. Scale it confidently; this setup’s battle-tested across wikis and forums. Dive in—your dotfiles deserve the glow-up.