---
id: db42bcef-a612-436f-9d14-72540f8acf0a
title: 'Small robustness/consistency batch: ''.'' repo name, gitlink pages, inbox list escaping, content-type anchor, symlink gap'
state: closed
labels:
- polish
assignees: []
milestone: null
created: 2026-10-27T14:54:31.668727942Z
updated: 2026-10-27T16:37:20.531268962Z
---
Smaller robustness/consistency items found in the same review. Each is independently shippable.

1. **`hu13 build .` loses the repo name.** `resolve_repo_name` uses `repo.file_name()`, which is `None` for `"."`, so the site is titled "repo" instead of the directory name. Cosmetic, but `.` is the obvious invocation and it silently degrades the header. Fix: canonicalize (or `repo.absolutize`) before taking `file_name`, or fall back to the current dir name when `None`.

2. **Submodule (gitlink) entries render as a bogus file.** A tree entry with mode `160000` is not a blob; `git show <branch>:<path>` on it returns the *commit object* text, so the build writes:
   - `raw/vendor/dep` = `commit a5e5260…\nAuthor: …` (looks like file content, is not)
   - `files/vendor/dep.html` = a file page containing that text
   Fix: skip/detach gitlinks in `ls_tree` (mark them), render a "submodule at <sha>" page, and never write them to `raw/`.

3. **`inbox list` prints the id unescaped.** `cmd_list` does `println!("{} [{}] {} {}", e.id, escape_terminal(kind), …)` — every other field goes through `escape_terminal`, `e.id` comes from the file stem. A `*.json` file dropped/pulled into the inbox dir with a name containing ESC/control bytes prints raw ANSI. `cmd_show` escapes its id, so this is just an inconsistency: escape it too.

4. **`content_type_for` matches `ends_with("/head")`.** That is any file named `head` anywhere in the tree, not just a bare repo's `HEAD`. Anchor the rule to the real paths (`"repo.git/HEAD"`, `"repo.git/info/refs"`, `"repo.git/info/packs"`, `"repo.git/packed-refs"`) or check the basename with `Path::file_name()`.

5. **Symlink handling is inconsistent across `.forge/` readers.** `wiki` skips symlinks explicitly and `collect_artifacts` skips them, but `forge::load` and `releases::load` `fs::read_to_string` the path directly, following symlinks. Today the content must still parse as frontmatter to reach output, so it is not an exploit — it is an invariant gap in the "never follow links out of the repo" rule. Make all four use one `read_repo_file` helper that rejects symlinks (and `..` components).

6. **`--repo-name`/name escaping is fine** (verified: a directory named `"><img src=x onerror=…>` renders escaped in `<title>`/`<h1>`) — recording it here so future refactors keep it.

Acceptance:
- [ ] `hu13 build .` titles the site with the directory name
- [ ] gitlink entries do not produce `raw/` files
- [ ] `inbox list` escapes the id
- [ ] content-type rule is path-anchored
- [ ] one symlink-rejecting reader shared by forge/releases/wiki/artifacts
