[{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":"When maintaining a site that publishes a new post every day, the first thing worth confirming is where the automation actually runs — otherwise it\u0026rsquo;s easy to go debugging the wrong place.\nTwo different patterns # A few earlier pipelines (insight, invest-news, etc.) followed a familiar shape:\na pipeline/ directory with a collector script in the repo a cron-triggered workflow under .github/workflows/ Actions handling collect → generate → commit → deploy end to end But a site built later had none of that — no pipeline/ directory, no collector script anywhere in the repo. The Actions workflow there did only build and deploy.\nCause # Publishing for that site was owned by a Claude Cloud Routine. The routine itself performs the web search and writes the Markdown post, then pushes it — and that push is what triggers the Actions build. In other words, the logic deciding what to write lived outside the repo entirely, on Claude\u0026rsquo;s own schedule.\n# searching the repo turns up nothing about the schedule or routine ID grep -r \u0026#34;cron\u0026#34; .github/workflows/ # only the build/deploy workflow shows up find . -name \u0026#34;pipeline\u0026#34; # nothing How to check and manage it # Routines can only be managed at https://claude.ai/code/routines (deletion included — only possible there). The schedule is a fixed-UTC cron, which matters across DST transitions. E.g. 0 11 * * * UTC is 06:00 CDT, but once the clocks fall back to CST that becomes 05:00 local — the cron needs updating to 0 12 * * * to keep it at 6 AM. To change publishing format or content, edit the repo\u0026rsquo;s root CLAUDE.md rather than the routine\u0026rsquo;s prompt — if the routine is designed to read that file to decide its behavior. Takeaway # When inheriting a site with automated publishing, don\u0026rsquo;t assume \u0026ldquo;no cron workflow visible\u0026rdquo; means \u0026ldquo;manually published.\u0026rdquo; A Claude Cloud Routine living outside the repo is an increasingly common shape for this.\n","date":"17 August 2026","externalUrl":null,"permalink":"/posts/claude-cloud-routine-vs-github-actions-cron/","section":"Posts","summary":"A repo with no visible cron workflow isn’t proof there’s no automated publishing. The schedule and generation logic can live entirely outside the repo, on Claude’s side.","title":"Claude Cloud Routine vs. GitHub Actions cron: they look the same but you manage them differently","type":"posts"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/claude-code/","section":"Tags","summary":"","title":"Claude-Code","type":"tags"},{"content":"Short write-ups of problems and fixes hit while building things with Claude Code. A running log of trial and error across Hugo, GitHub Pages, Docusaurus, Vercel, and more.\n","date":"17 August 2026","externalUrl":null,"permalink":"/","section":"Codes","summary":"","title":"Codes","type":"page"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/github-actions/","section":"Tags","summary":"","title":"Github-Actions","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"17 August 2026","externalUrl":null,"permalink":"/tags/trial-and-error/","section":"Tags","summary":"","title":"Trial-and-Error","type":"tags"},{"content":"","date":"16 August 2026","externalUrl":null,"permalink":"/tags/dns/","section":"Tags","summary":"","title":"Dns","type":"tags"},{"content":"A checklist assembled after repeating the same \u0026ldquo;why isn\u0026rsquo;t this one working\u0026rdquo; cycle across several *.metacog.co.kr subdomains on GitHub Pages.\nThe easy mistake # It\u0026rsquo;s tempting to think filling in the repo side (a static/CNAME file, the Pages API\u0026rsquo;s cname field) is the whole job. It\u0026rsquo;s only half of it. Adding the actual CNAME record at the domain registrar\u0026rsquo;s DNS is a separate, and manual, step.\nCause # GitHub can\u0026rsquo;t issue a Let\u0026rsquo;s Encrypt certificate until the DNS record genuinely points that subdomain at jeonck.github.io.. Until then, https_enforced stays false, and hitting the site over HTTPS either throws a certificate error or 404s.\nThe order that works # Add a static/CNAME file to the repo with just the subdomain.\ncodes.metacog.co.kr Add the CNAME record directly in the registrar\u0026rsquo;s DNS panel (usually not something an agent can do via API/CLI).\ncodes CNAME jeonck.github.io. Wait for propagation, then confirm.\ndig +short codes.metacog.co.kr # passing once this returns jeonck.github.io. Only once propagation is confirmed, turn on HTTPS enforcement.\ngh api repos/jeonck/\u0026lt;repo\u0026gt;/pages --method PUT --field https_enforced=true Verifying the deploy before DNS has propagated # Deployment correctness can be checked independently of DNS propagation by pinning the resolved IP directly:\ncurl --resolve codes.metacog.co.kr:80:185.199.108.153 http://codes.metacog.co.kr/ That separates \u0026ldquo;the site itself is broken\u0026rdquo; from \u0026ldquo;just waiting on DNS\u0026rdquo; as two distinct failure modes.\n","date":"16 August 2026","externalUrl":null,"permalink":"/posts/github-pages-custom-subdomain-dns-checklist/","section":"Posts","summary":"The repo can have its CNAME file and the Pages API can have its cname field set — but without an actual DNS record at the registrar, certificate issuance stays blocked.","title":"GitHub Pages custom subdomains: static/CNAME alone is never enough","type":"posts"},{"content":"","date":"16 August 2026","externalUrl":null,"permalink":"/tags/github-pages/","section":"Tags","summary":"","title":"Github-Pages","type":"tags"},{"content":"","date":"16 August 2026","externalUrl":null,"permalink":"/tags/https/","section":"Tags","summary":"","title":"Https","type":"tags"},{"content":"","date":"14 August 2026","externalUrl":null,"permalink":"/tags/architecture/","section":"Tags","summary":"","title":"Architecture","type":"tags"},{"content":"Adding sites one after another under the same account eventually raises the question: \u0026ldquo;doesn\u0026rsquo;t GitHub Pages only support one site per account?\u0026rdquo; In practice, a repo can host as many sites as needed — it just comes down to understanding how the domain routing works.\nThe structure # Every site under the account lives in its own GitHub repo, and each repo has a CNAME file holding a distinct \u0026lt;subdomain\u0026gt;.metacog.co.kr value.\njeonck/talktime → static/CNAME → talktime.metacog.co.kr jeonck/insight → static/CNAME → insight.metacog.co.kr jeonck/codes → static/CNAME → codes.metacog.co.kr On the DNS side, each of those subdomains is a CNAME record pointing at jeonck.github.io (the nameservers live with the domain registrar, and there\u0026rsquo;s no wildcard record — every subdomain gets added individually). When a request comes in, GitHub looks at the Host header and matches it against the CNAME file of each repo, then serves whichever one matches. That\u0026rsquo;s what lets many repos share a single Pages account without colliding.\nAdding a new site # A new subdomain site starts with a new repo — not a subdirectory of an existing site.\n# 1. Create the repo, commit the CNAME echo \u0026#34;codes.metacog.co.kr\u0026#34; \u0026gt; static/CNAME git add static/CNAME \u0026amp;\u0026amp; git commit -m \u0026#34;Add custom domain\u0026#34; # 2. Enable Pages, serving from the repo root on main gh api -X POST repos/jeonck/codes/pages \\ -f \u0026#39;source[branch]=main\u0026#39; -f \u0026#39;source[path]=/\u0026#39; # 3. Only enforce HTTPS once the DNS record is confirmed live dig +short codes.metacog.co.kr Common misconception # \u0026ldquo;Can\u0026rsquo;t a new site just be a subdirectory under one portfolio repo?\u0026rdquo; — no. Subdomain routing operates at the repo level, not the path level. The account\u0026rsquo;s profile README repo (jeonck/jeonck) is not a site under this pattern — it\u0026rsquo;s the special repo GitHub uses for the profile page, worth remembering separately so it doesn\u0026rsquo;t get confused with the rest. ","date":"14 August 2026","externalUrl":null,"permalink":"/posts/one-github-pages-repo-per-subdomain/","section":"Posts","summary":"It’s easy to assume GitHub Pages only really serves one thing per account. In practice it’s the CNAME file that decides routing.","title":"One subdomain = one GitHub Pages repo — running many sites off a single account","type":"posts"},{"content":"It\u0026rsquo;s easy to assume commit message language is consistent by default — until one repo turned out not to be.\nThe mismatch found # The site itself was English-facing, but its commit log was mostly Korean. The outward-facing text (the site) and the not-outward-facing text (the commit history) had drifted into different languages.\nDecision # Scope: commit messages only. Code comments and chat replies stay Korean. New commits only, going forward. The six existing Korean commits were left untouched — force-pushing a public branch to rewrite history for a purely cosmetic reason isn\u0026rsquo;t worth the risk. # history stays as-is. none of this: git rebase -i --root git push --force The easy-to-miss spot: commits automation generates # It\u0026rsquo;s tempting to think the job is done once human-authored commits switch to English. This repo also had a daily GitHub Actions workflow that generates its own commit message. Miss that template, and the automated commits keep landing in Korean.\n# .github/workflows/daily.yml — the part that got missed - run: | git commit -m \u0026#34;데일리 업데이트: $(date +%F)\u0026#34; # this needed fixing too git commit -m \u0026#34;Daily update: $(date +%F)\u0026#34; Takeaway # Changing a commit message convention means checking both \u0026ldquo;commits a human types\u0026rdquo; and \u0026ldquo;commits automation types\u0026rdquo; separately. The latter is quick to find with a grep across the repo\u0026rsquo;s workflows.\ngrep -rn \u0026#34;git commit -m\u0026#34; .github/workflows/ ","date":"11 August 2026","externalUrl":null,"permalink":"/posts/commit-message-language-per-repo-convention/","section":"Posts","summary":"An English-facing site had commits piling up in Korean. Switching new commits to English seemed simple, until one easy-to-miss spot turned up.","title":"Commit message language is a per-repo decision — and the automation template needs updating too","type":"posts"},{"content":"","date":"11 August 2026","externalUrl":null,"permalink":"/tags/convention/","section":"Tags","summary":"","title":"Convention","type":"tags"},{"content":"","date":"11 August 2026","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"Fully static hosting like GitHub Pages sometimes still needs a \u0026ldquo;click a button, an LLM generates something, the site reflects it\u0026rdquo; feature. The recurring question is where the credentials live.\nThe two options proposed first (both declined) # API key in browser localStorage — the user pastes a key and the client calls the LLM directly. Simplest option, but the key is exposed client-side. Proxy through a Cloudflare Worker — the key stays hidden, but now there\u0026rsquo;s a whole extra piece of infrastructure to run (and pay for), and the Worker itself becomes a new attack surface. Both options share the same shape: either trust something in the browser, or stand up a new server. Neither was the direction wanted.\nThe pattern that was chosen: GitHub issues as the trigger # The browser\u0026rsquo;s only job is opening a GitHub issue. Actual execution happens inside GitHub Actions, using a token that already lives in the repo.\n\u0026#34;Add Transcript ✏️\u0026#34; button on the site │ (click navigates to a GitHub issue form) ▼ GitHub issue created (the user authenticates via their own GitHub account) │ the issues event triggers a workflow ▼ GitHub Actions workflow │ runs the agent using a repo secret token ▼ Result gets committed → site rebuilds No secret ever touches the browser. Authentication is GitHub\u0026rsquo;s own login; execution privilege exists only inside the Actions workflow.\nThe gate a public repo needs # On a public repo, an issue-triggered workflow can be fired by anyone who opens an issue. An owner-only guard has to be the first thing the workflow checks.\njobs: run-agent: if: github.event.issue.user.login == github.repository_owner runs-on: ubuntu-latest steps: - run: echo \u0026#34;owner-only guard passed\u0026#34; When to reach for this pattern # When a request comes in for \u0026ldquo;AI functionality on a static page,\u0026rdquo; don\u0026rsquo;t lead with a browser-stored-token approach. This structure is already validated — offer it as the default.\n","date":"30 July 2026","externalUrl":null,"permalink":"/posts/static-site-llm-feature-github-issue-trigger/","section":"Posts","summary":"Two options came up first — an API key in localStorage, or a Cloudflare Worker proxy — and neither was it. A GitHub-issue trigger pattern was requested instead.","title":"Adding an LLM feature to a static site without ever putting a token in the browser","type":"posts"},{"content":"","date":"30 July 2026","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":"What to check when a third-party skill was just installed but isn\u0026rsquo;t recognized by the current session.\nSymptom # npx skills add Vincentwei1021/video-shotcraft The command exits successfully and the files land on disk, but the skill never shows up in /help or the session\u0026rsquo;s available-skills list.\nCause # npx skills add installs into ~/.agents/skills/\u0026lt;skill-name\u0026gt;/, not ~/.claude/skills/. That path is shared between Claude Code and Codex. On top of that, an already-running session doesn\u0026rsquo;t rescan the skills directory — a newly installed skill only shows up after starting a new session.\nls ~/.agents/skills/ # video-shotcraft shows up here (note: the repo name is \u0026#34;shotcraft\u0026#34;, no \u0026#34;r\u0026#34;) Using it right away, without a new session # No need to wait for a new session — just read the SKILL.md directly and follow it in place.\ncat ~/.agents/skills/video-shotcraft/SKILL.md Takeaway # \u0026ldquo;Installed but not showing up\u0026rdquo; usually isn\u0026rsquo;t a failed install — it\u0026rsquo;s one of two things: wrong path assumption (checking the legacy ~/.claude/skills/), or a session that hasn\u0026rsquo;t rescanned. Neither needs a reinstall, just a check.\n","date":"26 July 2026","externalUrl":null,"permalink":"/posts/npx-skills-add-real-install-path/","section":"Posts","summary":"The install command exits clean and the files exist on disk, but the skill still won’t show up in the session — check the path first.","title":"npx skills add installs to ~/.agents/skills, not ~/.claude/skills","type":"posts"},{"content":"","date":"26 July 2026","externalUrl":null,"permalink":"/tags/skills/","section":"Tags","summary":"","title":"Skills","type":"tags"},{"content":"","date":"7 July 2026","externalUrl":null,"permalink":"/tags/hextra/","section":"Tags","summary":"","title":"Hextra","type":"tags"},{"content":"","date":"7 July 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"On a docs site built with Hugo Hextra, some in-content relative links only break after deploy. If hugo server looks fine locally but the same page 404s on GitHub Pages, this is a likely cause.\nCause # Hextra\u0026rsquo;s link render hook (themes/hextra/layouts/_markup/render-link.html) only rewrites an internal link into a clean URL if the link target ends in .md.\n{{/* summary of the logic inside render-link.html */}} {{ if strings.HasSuffix $url.Path \u0026#34;.md\u0026#34; }} {{/* .GetPage resolves the page and rewrites to a clean URL */}} {{ end }} A link without .md never enters that branch — it\u0026rsquo;s emitted verbatim into the HTML. So [lab](../ci-cd/lab-github-actions) isn\u0026rsquo;t resolved at all; under pretty-URL routing that literal path doesn\u0026rsquo;t exist, hence the 404.\nThe rule # Always suffix internal links with .md, whether they point at a page or a section.\n\u0026lt;!-- page link --\u0026gt; [GitHub Actions lab](../ci-cd/lab-github-actions.md) \u0026lt;!-- section (directory) link --\u0026gt; [CI/CD section](../ci-cd/_index.md) Catching every broken link before deploy # Rather than clicking through pages one by one, grep for every relative link missing .md:\ngrep -rnoE \u0026#39;\\]\\([^)]+\\)\u0026#39; content/ \\ | grep -vE \u0026#39;https?:|#|mailto:\u0026#39; \\ | grep -v \u0026#39;\\.md\u0026#39; An empty result means it\u0026rsquo;s safe to ship. This rule isn\u0026rsquo;t called out prominently in Hextra\u0026rsquo;s own docs, so it tends to eat a fair bit of debugging time the first time it\u0026rsquo;s hit.\n","date":"7 July 2026","externalUrl":null,"permalink":"/posts/hextra-relative-link-must-end-in-md/","section":"Posts","summary":"An extensionless relative link like lab renders fine locally but 404s under GitHub Pages’ pretty URLs.","title":"Hugo Hextra theme: in-content relative links must end in .md","type":"posts"},{"content":"Scaffolding a Next.js project can hit a wall where nothing looks misconfigured, yet both scaffolding and deploy fail.\nSymptom # The working directory was named CTI. Scaffolding and deploying straight from there:\ncd /Users/mac/ws/claude/CTI npx create-next-app@latest . # fails name validation vercel deploy # also fails name validation Cause # Neither create-next-app nor Vercel allow uppercase characters in a project/app name. Both tools tried to reuse the directory name as the project name and rejected it at validation.\nFix # Rather than renaming the directory (it was already in use for other things and renaming wasn\u0026rsquo;t desirable), the project was scaffolded with a lowercase name in a temp location, then moved into the original directory — and the deploy target was linked explicitly with a lowercase project name via vercel link.\n# 1. Scaffold with a lowercase name in a temp location npx create-next-app@latest /tmp/cti-sentinel # 2. Move the generated files into the original uppercase directory mv /tmp/cti-sentinel/* /Users/mac/ws/claude/CTI/ # 3. Deploy by linking to an explicit lowercase project name (avoids automatic name inference) cd /Users/mac/ws/claude/CTI vercel link --project cti-sentinel --yes vercel deploy Takeaway # The local directory name and the deployed project name are independent — use that to get around the validation rule without ever renaming the directory itself, via vercel link --project \u0026lt;lowercase-name\u0026gt;.\n","date":"6 July 2026","externalUrl":null,"permalink":"/posts/vercel-deploy-fails-on-uppercase-folder-name/","section":"Posts","summary":"A project folder named CTI trips up both create-next-app and vercel deploy on name validation.","title":"If Vercel deploy fails for no obvious reason, check for uppercase in the folder name","type":"posts"},{"content":"","date":"6 July 2026","externalUrl":null,"permalink":"/tags/nextjs/","section":"Tags","summary":"","title":"Nextjs","type":"tags"},{"content":"","date":"6 July 2026","externalUrl":null,"permalink":"/tags/vercel/","section":"Tags","summary":"","title":"Vercel","type":"tags"},{"content":"","date":"16 June 2026","externalUrl":null,"permalink":"/tags/docusaurus/","section":"Tags","summary":"","title":"Docusaurus","type":"tags"},{"content":"","date":"16 June 2026","externalUrl":null,"permalink":"/tags/latex/","section":"Tags","summary":"","title":"Latex","type":"tags"},{"content":"On a Docusaurus project with future: { v4: true } (MDX v3) enabled, adding math notation ($...$, $$...$$) fails in a specific, repeatable way.\nSymptom # npm install remark-math@6 rehype-katex@7 Wiring the plugins into docusaurus.config.ts exactly as documented still produces an acorn parse error at build time.\nCause # MDX v3\u0026rsquo;s micromark-extension-mdx-expression runs before remark-math. So a { inside a block like $$ C = M^e \\mod n $$ gets mistaken for the start of a JSX expression and parsing fails right there. No combination of remark-math/rehype-katex versions fixes this — it\u0026rsquo;s an execution-order issue, not a version-compat issue.\nFix: drop it, use code spans + Unicode instead # Don\u0026rsquo;t fight it — route around it.\nInline math as code: `C = M^e mod n` Greek letters typed directly as Unicode: φ(n) = (p-1)(q-1) Best to not add remarkMath / rehypeKatex / the KaTeX CSS to docusaurus.config.ts at all — it saves debugging time. This only bites on future.v4 (MDX v3); check whether any docs lean heavily on math notation before migrating to it.\n","date":"16 June 2026","externalUrl":null,"permalink":"/posts/docusaurus-mdx-v3-latex-broken/","section":"Posts","summary":"Installing remark-math + rehype-katex exactly by the book still breaks the build, because braces inside $$...$$ get parsed as JSX expressions.\n","title":"LaTeX just doesn't work under Docusaurus MDX v3 (future.v4) — stop fighting it","type":"posts"},{"content":"","date":"16 June 2026","externalUrl":null,"permalink":"/tags/mdx/","section":"Tags","summary":"","title":"Mdx","type":"tags"},{"content":"Referencing an index.md file in Docusaurus\u0026rsquo;s manual sidebar (the kind you list explicitly in sidebars.ts) gets surprisingly fiddly once numeric-prefixed directories are involved.\nThe problem # The directory structure looked like this:\ndocs/01-foundations/01-cryptography/index.md The obvious-looking candidates were tried, in order, and all of them failed.\n// attempt 1 \u0026#34;01-foundations/01-cryptography/index\u0026#34; // attempt 2 \u0026#34;foundations/cryptography\u0026#34; // attempt 3 \u0026#34;01-foundations\u0026#34; Cause and rule # Docusaurus derives a sidebar ID with two rules:\nNumeric prefixes (01-, 02-, …) are stripped from the ID. index.md files keep an /index suffix on the ID. So the correct ID was:\nfoundations/cryptography/index The faster way to find it # Instead of guessing frontmatter values, get the autogenerated sidebar to build successfully first, then read the real ID straight out of the build artifacts.\n# 1. Temporarily switch sidebars.ts to autogenerated mode and confirm the build succeeds npm run build # 2. Read the real doc IDs out of the build output cat .docusaurus/globalData.json | grep -o \u0026#39;\u0026#34;id\u0026#34;:\u0026#34;[^\u0026#34;]*\u0026#34;\u0026#39; | sort -u Copy whatever ID shows up there straight into the manual sidebar.\nOne thing to watch # Setting a custom id: in frontmatter overrides the rule above entirely. Don\u0026rsquo;t mix a manual sidebar with custom IDs — the moment they mix, you\u0026rsquo;re back to debugging from scratch.\n","date":"15 June 2026","externalUrl":null,"permalink":"/posts/docusaurus-manual-sidebar-doc-id/","section":"Posts","summary":"Every plausible-looking ID like 01-foundations/01-cryptography/index turned out wrong. The real ID was sitting in the build output the whole time.","title":"Finding the real doc ID for index.md in a Docusaurus manual sidebar","type":"posts"},{"content":"","date":"15 June 2026","externalUrl":null,"permalink":"/tags/sidebar/","section":"Tags","summary":"","title":"Sidebar","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]