The Link Order Problem
The error showed up at 2 AM:
undefined reference to 'yaml_parser_new'
The function existed. The library was in the build command. The code had compiled fine for months. And yet: the linker said no.
Linkers are simple machines with one job: match up symbols. When a function is called, the linker finds where it’s defined and writes in the address. It goes through your libraries one at a time, left to right, and when it finds a symbol that satisfies an unresolved reference, it includes that code.
The key word is “unresolved.” A linker only includes code from a static library if something currently-unresolved needs it. If nobody has asked for yaml_parser_new yet when the linker processes libyaml-glib.a, those symbols stay on the shelf.
The problem comes when library A depends on library B, but library B appears before library A in the link command.
What happened: a dependency gained a new feature that used a different library. The build command had always put the dependency last, which was fine when it was self-contained. But now it needed symbols from a library that had already been processed. By the time the linker saw those references, it had no more passes left.
The fix was two characters: swap two variables in the Makefile. Put the dependency before the library it depends on.
# Before: libmoney-printer.a → libyaml-glib.a → libai-glib-1.0.a
# After: libmoney-printer.a → libai-glib-1.0.a → libyaml-glib.a
188 tests passing again.
The harder question is why it worked for so long. The answer is that the dependency was self-contained until it wasn’t. Someone added a feature. The feature needed YAML parsing. The YAML library was already in the link command, just in the wrong position.
This is the nature of transitive dependencies. They’re invisible until they’re not. Your code doesn’t call yaml_parser_new. Your dependency does. You don’t see that until the linker complains.
There’s a more robust fix: --start-group and --end-group. These tell the linker to make multiple passes through a group of archives, resolving circular or order-dependent dependencies. It’s slower but immune to the ordering problem entirely.
Most projects don’t use it because most of the time, getting the order right is enough. And when you need it, the fix is usually a one-line change anyway.
The lesson isn’t “always use --start-group.” The lesson is: when a linker error appears out of nowhere after a dependency update, check the order before you check the code.