Skip to content

Commit f0c3bd8

Browse files
author
lzm
committed
feat(blog): add English translation of 'Hugo blog build pitfalls'
Pairs with the Chinese version via filename + same slug (hugo-blog-build-pitfalls). Hugo auto-detects translations when the filename matches across language trees. Uses a separate series name 'Blog Setup' so English readers see a natural-language term instead of Chinese characters. The Chinese '博客搭建' series and the English 'Blog Setup' series are independent — each has its own /series/<name>/ listing. Covers the same content as the Chinese post: - Parallel-agent race condition in git history - The four-step giscus debug chain ending in repoId format mismatch - Smaller setup potholes and retrospective
1 parent 1b01c01 commit f0c3bd8

1 file changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
---
2+
title: "Hugo + PaperMod Blog Setup: From Zero to Comments Live — The Potholes I Hit"
3+
date: 2026-08-30T17:17:00+08:00
4+
draft: false
5+
slug: hugo-blog-build-pitfalls
6+
description: "Real problems I hit while building this Hugo blog: the race condition that polluted my git history when parallel agents ran, and the four-hour debug of giscus's 'Unable to create discussion' — which turned out to be a repoId format mismatch."
7+
tags:
8+
- Hugo
9+
- PaperMod
10+
- Giscus
11+
- Debugging
12+
categories:
13+
- Essays
14+
series:
15+
- Blog Setup
16+
ShowToc: true
17+
TocOpen: true
18+
---
19+
20+
## Why this post
21+
22+
In my previous post I said I wanted to start a blog to record what I learn. This one is about how I actually built the blog itself — and the real problems I hit along the way.
23+
24+
The stack I picked is plain: **Hugo** (static site generator) + **PaperMod** (theme) + **GitHub Pages** (hosting) + **Giscus** (comments, backed by GitHub Discussions).
25+
26+
In theory this is a 30-minute setup. In practice it took an entire afternoon plus an evening — most of it burned on a problem that looked like a config issue but was actually something deeper: Giscus kept reporting `Unable to create discussion` no matter what I changed.
27+
28+
Here's the play-by-play.
29+
30+
## 1. Sixteen features, eleven parallel agents
31+
32+
After the first post went live, I lined up a long wishlist:
33+
34+
- Series support (taxonomy + homepage cloud + prev/next nav)
35+
- Search box click bug
36+
- Homepage taxonomy cloud (categories + tags)
37+
- Custom shortcodes (alert / card / video)
38+
- Syntax highlighting (chroma CSS)
39+
- Code block copy buttons
40+
- Giscus comments
41+
- Mermaid diagrams + KaTeX math
42+
- Share buttons (Weibo + copy link)
43+
- Multi-language support (add English)
44+
- Rainbow-colored tags
45+
- Tech-feel icon glow
46+
- …and a few more
47+
48+
That's 16 items. I dispatched **eleven agents in parallel**, each with a disjoint file scope, each told to commit + push when finished.
49+
50+
### First pothole: race condition
51+
52+
The biggest problem with running agents in parallel is that **their commits can interleave**.
53+
54+
Say agent A and agent B both touch `hugo.yaml`. A commits first. B then cherry-picks A's work — but A's working tree has files that A *intended to commit later*. B's `git add` happily grabs those files too.
55+
56+
The result: two commits whose messages don't match their actual diffs. Git history gets corrupted.
57+
58+
{{< alert type="warning" >}}
59+
**Lesson**: when multiple agents share a working tree, either give each one its own git worktree, or enforce atomic "edit → `git add``git commit`" with no intermediate state.
60+
{{< /alert >}}
61+
62+
In the end I used `git reset --hard` + cherry-picked all six commits back, rewriting the messages with a NOTE clarifying what each one really contained. After force-push, history was clean.
63+
64+
## 2. Giscus "Unable to create discussion" — a 30-minute debug
65+
66+
I picked Giscus for comments because:
67+
68+
- No backend to run
69+
- Comments live in GitHub Discussions — **never get lost**
70+
- Comments are issues — searchable by Google
71+
- Open source, free
72+
73+
Following Giscus's official steps: install the giscus app → enable Discussions → copy config — should take three minutes.
74+
75+
After deploy, the comment widget kept showing:
76+
77+
```
78+
Unable to create discussion
79+
```
80+
81+
Opened Edge DevTools, found a sea of red:
82+
83+
```
84+
POST https://giscus.app/api/discussions 400 (Bad Request)
85+
onDiscussionCreateRequest @ widget-...js:1
86+
```
87+
88+
### Diagnosis 1: Category permissions
89+
90+
First instinct: is it a category permission issue? GitHub's default **Announcements** category is maintainer-only — regular users can't post.
91+
92+
Switched from `announcements` to `general` (open to all logged-in GitHub users), pushed, redeployed, refreshed — **still 400**.
93+
94+
### Diagnosis 2: Browser settings
95+
96+
Second instinct: is Edge's Tracking Prevention blocking third-party cookies?
97+
98+
Changed it to "Basic" level, restarted the browser — **still 400**.
99+
100+
### Diagnosis 3: Giscus backend session
101+
102+
Third instinct: does Giscus's backend not have my session?
103+
104+
Hit the Giscus API directly with curl:
105+
106+
```bash
107+
curl -X POST https://giscus.app/api/discussions \
108+
-H "Content-Type: application/json" \
109+
-d '{}'
110+
```
111+
112+
Got back:
113+
114+
```json
115+
{ "error": "Invalid or missing access token." }
116+
```
117+
118+
But I'd already clicked "Sign in" in the browser. The session should be there. The issue isn't the session.
119+
120+
### Diagnosis 4: Read the source
121+
122+
I pulled Giscus's source from GitHub and read through it. Found the key code in `pages/api/discussions/index.ts`:
123+
124+
```typescript
125+
async function post(req, res) {
126+
// 1. Validate user token — passed ✅
127+
const userToken = req.headers.authorization?.split('Bearer ')[1];
128+
if (!(await check(userToken))) {
129+
res.status(403).json({ error: 'Invalid or missing access token.' });
130+
return;
131+
}
132+
133+
// 2. Get giscus-app's installation token for this repo — passed ✅
134+
let token: string;
135+
try {
136+
token = await getAppAccessToken(repo);
137+
} catch (error) {
138+
res.status(403).json({ error: error.message });
139+
return;
140+
}
141+
142+
// 3. Call GitHub GraphQL createDiscussion mutation
143+
const response = await createDiscussion(token, params);
144+
const id = response?.data?.createDiscussion?.discussion?.id;
145+
146+
if (!id) {
147+
res.status(400).json({ error: 'Unable to create discussion with request body.' });
148+
return;
149+
}
150+
151+
res.status(200).json({ id });
152+
}
153+
```
154+
155+
**Key finding**: the 400 comes from **step 3**`createDiscussion` returns JSON with an empty `discussion.id`.
156+
157+
In other words: **GitHub's GraphQL API is rejecting the mutation**.
158+
159+
### Diagnosis 5: repoId format
160+
161+
Queried my repo's node ID directly via GraphQL:
162+
163+
```bash
164+
gh api graphql -F query='
165+
query {
166+
repository(owner:"LiMingCoding", name:"LiMingCoding.github.io") {
167+
id
168+
}
169+
}'
170+
```
171+
172+
Got back:
173+
174+
```json
175+
{ "data": { "repository": { "id": "MDEwOlJlcG9zaXRvcnkzODczMjk0MDY=" } } }
176+
```
177+
178+
But what I had in my config was:
179+
180+
```yaml
181+
giscus:
182+
repoId: "387329406" # ❌ wrong!
183+
```
184+
185+
**The actual root cause**:
186+
187+
| ID format | Use |
188+
|---|---|
189+
| `387329406` | REST API numeric database ID |
190+
| `MDEwOlJlcG9zaXRvcnkzODczMjk0MDY=` | GraphQL node ID (base64) |
191+
192+
Giscus's "Configure" page **gave me the REST numeric ID**, but the backend calls **GraphQL mutation** — which needs the node ID format.
193+
194+
After fixing it, pushing, deploying, and refreshing — the comment went through 🎉
195+
196+
{{< alert type="success" >}}
197+
**Lesson**: the `repoId` on giscus.app's config page is buggy — it displays the numeric format but the backend wants base64. **Look it up yourself via `gh api graphql`** — that's the reliable way.
198+
{{< /alert >}}
199+
200+
## 3. A few smaller potholes
201+
202+
| Problem | Fix |
203+
|---|---|
204+
| GitHub Discussions not enabled | Repo Settings → General → Features → check Discussions |
205+
| Giscus app not installed or wrong perms | github.com/apps/giscus → Install → pick repo → check Read+Write |
206+
| Edge's default Tracking Prevention blocking | edge://settings/privacy → switch to Basic |
207+
| PaperMod version too old for `series` taxonomy | PaperMod ≥ some-recent-version supports it |
208+
209+
## Retrospective
210+
211+
1. **Race conditions with parallel agents can't be fully avoided** — unless you use git worktrees or stricter protocols. Next time I dispatch agents, I'll write "commit atomicity" into the prompt explicitly.
212+
213+
2. **Debugging third-party SaaS bugs** — services like Giscus that wrap GitHub's API hide their stack traces. Be willing to **read their source code** directly — it's faster than guessing.
214+
215+
3. **GraphQL and REST have two different ID systems** — for any future GitHub API call, confirm which one you're using and what ID format it needs.
216+
217+
## Next steps
218+
219+
- Full-text RSS output
220+
- Self-host a lightweight comment mirror (in case Giscus ever shuts down)
221+
- Flesh out the rest of the series posts (the opening one is too short 😅)

0 commit comments

Comments
 (0)