
At 1 AM, two screenshots landed in front of me with the least subtle bug report possible: the new logo I had just added to a project README was a broken-image box on my own site.
On GitHub, the README looked perfect. On ExploreCMS, the text looked perfect. The image alone had vanished.
That combination was the clue. The Markdown was valid. The file existed. The renderer worked. What changed was the place where the document lived.
The same Markdown can mean two different URLs
The README used a relative image path:

GitHub knows the context around that line. It knows the repository, the branch, and the directory containing the README. It can resolve the path to the actual file and serve the image.
My CMS had imported the raw Markdown and stored it as content. When the browser rendered that same path on tun.lat, it did the normal web thing: it treated the URL as relative to the current page. Instead of asking GitHub for the image, it asked my site for something shaped like:
/projects/HybridADManager/Resources/logo.png
That file was never there. The browser was behaving correctly. GitHub was behaving correctly. ExploreCMS was also doing exactly what I had built it to do: preserve the README text.
Unfortunately, preserving the text did not preserve its meaning.
Fix the boundary, not every README
The tempting fix was to edit that one README and replace the relative path with a full raw.githubusercontent.com URL. It would have repaired the logo immediately.
It also would have been the wrong fix.
Relative links are useful. They keep documentation portable across forks, branches, local clones, and renamed repositories. GitHub has supported them in markup files for years. The README author should not have to predict every external site that might import the document later.
The importer is the component crossing the boundary, so the importer should translate the paths.
I added a URL-resolution step to the GitHub sync flow. Before ExploreCMS stores imported Markdown, it now rewrites repository-relative targets using the repository owner, name, and branch already available from the GitHub response.
The mapping is deliberately different for images and ordinary links:
- Relative images become raw-content URLs, so the browser receives the actual image bytes.
- Relative file links become GitHub
blobURLs, so a click opens the file in its repository context. - Already absolute URLs, anchors, mail links, and data URLs stay untouched.
That last rule matters. A URL rewriter that “helpfully” rewrites everything is just a second bug wearing a utility-function hat.
One helper, three doors
ExploreCMS had three separate paths that could bring GitHub content into the database. I initially found the obvious one, then checked the call sites and found the other two.
That turned a one-line symptom into a platform fix:
- initial repository import
- manual sync from GitHub
- the alternate project-sync path used by the admin workflow
All three now pass the Markdown through the same resolver.
This is the boring part of bug fixing that saves the next evening. If I had patched only the path triggered by my test, another import route would eventually produce the same broken logo and make the original fix look unreliable.
The helper also handles more than Markdown image syntax. READMEs can contain HTML <img> tags, and ordinary links such as (LICENSE) or (docs/api.md) have the same context problem. The first visible failure was a logo, but the sweep found several documentation and licence links that were quietly pointing at my own domain too.
Broken images shout. Broken links wait patiently for a visitor to click them.
Repairing the past as well as the future
The code fix protected future imports, but the broken Markdown was already stored in the database. Deploying the resolver would not retroactively rewrite existing project content.
So I did both jobs:
- shipped the resolver for every future import and sync
- patched the already imported READMEs so the live pages recovered immediately
Then I checked the public project page. The first request still showed the broken image.
For a brief moment, this looked like a second bug. It was cache revalidation doing stale-while-revalidate: the first request received the cached page while triggering an update in the background. The next request showed the repaired URL and the logo rendered correctly.
That distinction is worth remembering. “I deployed the fix and still see the bug” can mean the fix failed, the data stayed stale, or the page cache has not turned over yet. Those are three different systems, and refreshing harder is not a debugging method.
What this bug changed in my mental model
I used to think of imported Markdown as portable text. It is only portable when every relative reference travels with enough context to be understood.
A README is not just a string. Its meaning depends on a base location: repository, branch, and directory. Strip those away and paths that were perfectly valid become instructions for the wrong server.
My checklist for importing repository documentation now looks like this:
- Identify the source repository, branch, and document directory.
- Resolve relative images to a raw-content endpoint.
- Resolve relative files to human-viewable repository pages.
- Leave absolute, anchor, mail, and data URLs alone.
- Apply the transformation at every ingestion path.
- Migrate existing stored content, not only future content.
- Verify the rendered page after cache revalidation, not just the database value.
The logo was never broken. The Markdown was never broken. The missing piece was context, quietly discarded between two systems that each thought the other one had it.
That is the kind of bug I enjoy after it is fixed: small on the screen, structural underneath, and useful enough to permanently improve the importer.
Whenever content crosses a system boundary, what invisible context did it leave behind?



