Summary
- To resolve headers included from other headers, clangd requires a TU context
- Without context, it falls back on bare minimum implementation and fails to discover symbols / discovers false truths
- In header-only C++, this makes clangd almost useless without context
- As a result, Serena is unable to provide agents with the required structural information
Reproduction
I use platformio with ESP IDF and its clangd as my only C++ toolchain, which makes it harder to provide a full reproduction. However a C++ programmer will be able to put this in context easily, and this is not a toolchain-dependent issue per se.
include/Outer/Interface/Foo.hpp
#pragma once
#include "Outer/detail/Concepts.hpp"
namespace Outer {
struct Foo {
template <typename T>
requires detail::is_foo_like<T>
static void make(T &&obj) {
obj.foo();
}
};
} // namespace Outer
include/Outer/detail/Concepts.hpp
#pragma once
#include <concepts>
namespace Outer::detail {
template <typename T>
concept is_foo_like = requires(T x) {
{ x.foo() } -> std::same_as<void>;
};
} // namespace Outer::detail
src/main.cpp
#include "Outer/Interfaces/Foo.hpp"
struct X {
void foo() {}
};
extern "C" void app_main(void);
void app_main() { Outer::Foo::make(X{}); }
Explanation
Foo.hpp requires Concepts.hpp, but even in an editor, main.cpp must be opened before clangd is able to relate the two headers through compile_commands.json. When opening only Foo.hpp, a hover call on make will resolve to the Foo struct itself instead of the actual method because the is_foo_like concept is unknown.
### struct `Foo`
Size: 1 byte, alignment 1 byte
// In namespace Outer
struct Foo {}
The #include statement reports a "no such file" error from clangd.
If main.cpp is opened first, the output becomes
### static-method `make`
→ `void`
Parameters:
- `T && obj`
// In Foo
public: template <typename T>
requires detail::is_foo_like<T>
static void make(T &&obj)
The same happens in Serena: Project indexing opens the Foo.hpp header in isolation, and serena project index returns
ERROR 2026-04-18 14:53:59,626 solidlsp.ls_process:_read_ls_process_stderr:391 - [active_file=include/Outer/Interfaces/Foo.hpp] E[14:53:59.625] IncludeCleaner: Failed to get an entry for resolved path '' from include "Outer/detail/Concepts.hpp" : No such file or directory
Codex 5.4 behavior
Prompt:
Please use Serena MCP to resolve references to the make() method on the Foo struct. Do not fall back to text search either directly or via Serena; use ONLY LSP capabilties.
• Called serena.find_symbol({"name_path_pattern":"Foo/make","relative_path":"include/Outer/Interfaces/Foo.hpp","include_body":true,"max_matches":1})
└ []
• Called serena.get_symbols_overview({"relative_path":"include/Outer/Interfaces/Foo.hpp","depth":1,"max_answer_chars":4000})
└ {"Namespace": [{"Outer": {"Class": ["Foo"]}}], "File": ["Outer/detail/Concepts.hpp"]}
The initial direct symbol lookup didn’t match the method path [...]
One caveat: Serena’s index exposed Outer::Foo cleanly, but not make as a separately addressable symbol in this template/constrained method form [...]
CCodex also separately confirmed that this is exactly what happens when it tries to mirror how Serena is using clangd to build the symbols database - without TU context, many symbols fall back to something else or are otherwise not usable.
Summary
Reproduction
I use platformio with ESP IDF and its clangd as my only C++ toolchain, which makes it harder to provide a full reproduction. However a C++ programmer will be able to put this in context easily, and this is not a toolchain-dependent issue per se.
include/Outer/Interface/Foo.hpp
include/Outer/detail/Concepts.hpp
src/main.cpp
Explanation
Foo.hpprequiresConcepts.hpp, but even in an editor,main.cppmust be opened before clangd is able to relate the two headers throughcompile_commands.json. When opening onlyFoo.hpp, ahovercall onmakewill resolve to theFoostruct itself instead of the actual method because theis_foo_likeconcept is unknown.The
#includestatement reports a "no such file" error from clangd.If
main.cppis opened first, the output becomesThe same happens in Serena: Project indexing opens the
Foo.hppheader in isolation, andserena project indexreturnsCodex 5.4 behavior
Prompt:
CCodex also separately confirmed that this is exactly what happens when it tries to mirror how Serena is using clangd to build the symbols database - without TU context, many symbols fall back to something else or are otherwise not usable.