Add blog-hub-notify Action: file hub sync issues when posts publish - #42
Conversation
Closes #41. Implements the spoke side of the revised design in DollhouseResearch-website#39: on push to main touching _blog_posts/, extract each post's front matter and file a blog-hub-sync issue on the hub repo with a ready-to-paste blog_hub.yml entry. No auto-PR by design; the issue is the signal for an editorial pass. No-ops with a step-summary notice when BLOG_HUB_NOTIFY_TOKEN is unset, and dedupes against existing open sync issues per slug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f85cd8c54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - title: "${title}" | ||
| date: ${date} | ||
| summary: ${description} |
There was a problem hiding this comment.
Quote and escape the generated YAML scalars
When a description contains YAML-significant text such as : , stripping its front-matter quotes and emitting it as a plain scalar produces an invalid ready-to-paste entry. This already occurs for _blog_posts/dollhousemcp-console-tour.md, whose description causes mapping values are not allowed in this context; titles containing double quotes can similarly break the quoted title. Serialize or correctly quote and escape these values before embedding them.
Useful? React with 👍 / 👎.
|
|
||
| # First-parent diff so a merge from develop reports what actually | ||
| # landed on main. Added + modified posts only; deletions ignored. | ||
| mapfile -t files < <(git diff --name-only --diff-filter=AM HEAD^ HEAD -- '_blog_posts/*.md' || true) |
There was a problem hiding this comment.
Include renamed posts in the notification diff
When a published post file is renamed, Git classifies the change as R, so --diff-filter=AM returns neither the old nor the new path and the workflow exits without filing an issue. The hub can consequently retain the old dead canonical URL; include renamed destinations in the diff, or disable rename detection so the destination is treated as an addition while deletions remain ignored.
Useful? React with 👍 / 👎.
Review: blog-hub-notify ActionReviewed the new workflow end-to-end against the repo existing action-pinning conventions and against the actual front matter of the posts in Findings 1. 2. title is wrapped in quotes but summary is not. Any description containing a colon+space, a leading dash/hash/asterisk, or other YAML-significant character will produce an invalid ready-to-paste snippet, which undercuts the main value proposition of the issue body. None of the current descriptions happen to trip this, but it is a matter of when, not if. Suggest quoting the summary value the same way title is. 3. Neither 4. Dedup relies on GitHub fuzzy, eventually-consistent search (minor-moderate) 5. The What is good here
Test coverage No automated test exercises the embedded bash, which is understandable for a small ops workflow, and the PR description notes Generated with Claude Code |
- Fall back to excerpt when a post has no description (one existing post relies on it) - Quote and escape the summary and title in the generated snippet; several live summaries contain colons that would break unquoted YAML - Dedup by exact title against open blog-hub-sync issues instead of GitHub search (tokenization + index lag made search unreliable) - Recursive diff pathspec so posts in future subfolders are picked up Verified: front-matter extraction run against all 7 existing posts; YAML parses; bash -n clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2ff480ab2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # First-parent diff so a merge from develop reports what actually | ||
| # landed on main. Added + modified posts only; deletions ignored. | ||
| # Recursive under _blog_posts/ so posts in future subfolders count. | ||
| mapfile -t files < <(git diff --name-only --diff-filter=AM HEAD^ HEAD -- '_blog_posts/' | grep -E '\.md$' || true) |
There was a problem hiding this comment.
When one push advances main by multiple commits, such as a rebase-merged PR, this examines only the final commit and misses posts added or modified by earlier commits in the same push, even though those changes caused the workflow to trigger. Diff github.event.before through GITHUB_SHA and fetch enough history so every published post in the push is reported.
Useful? React with 👍 / 👎.
| infm && index($0, key ": ")==1 { | ||
| sub(key ": ", ""); print; exit | ||
| } | ||
| ' "$1" | sed -E 's/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/' |
There was a problem hiding this comment.
Parse front matter before re-encoding it
For valid quoted YAML containing escapes, removing only the outer quotes leaves the YAML encoding in the value and corrupts the generated entry. For example, title: "Agent \"Profiles\"" is emitted by the new yaml_dq path as a title containing literal backslashes, while a single-quoted contraction such as 'DollhouseMCP''s' retains the doubled apostrophe. This behavior of the newly added escaping path is fresh evidence beyond the prior review comment; parse the front matter as YAML before serializing its values.
Useful? React with 👍 / 👎.
Review:
|
- Diff github.event.before..sha instead of HEAD^..HEAD so multi-commit pushes can't silently skip a post (zero-SHA guarded via first parent) - Let a failing git diff fail the step; only zero .md matches is a no-op - Queue concurrent runs (concurrency group, no cancel) to close the double-file window on rapid pushes - Add workflow_dispatch with an explicit base/head range for dry runs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc987c14c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| concurrency: | ||
| group: blog-hub-notify | ||
| cancel-in-progress: false |
There was a problem hiding this comment.
Preserve every queued push notification
When three or more pushes arrive while the first run is active, GitHub Actions retains only one pending run per concurrency group and replaces the existing pending run with the newest one even when cancel-in-progress is false. Because each run processes only its own github.event.before..github.sha range, the replacement run does not cover changes unique to the canceled middle push, so a post from that push can remain permanently unreported. Use a mechanism that coalesces ranges or otherwise persists every push rather than relying on this concurrency group as a queue.
Useful? React with 👍 / 👎.
| gh issue create -R "$HUB_REPO" \ | ||
| --title "$issue_title" \ | ||
| --label blog-hub-sync \ | ||
| --body-file "$body_file" |
There was a problem hiding this comment.
Make the advertised dry run side-effect free
When a maintainer invokes workflow_dispatch expecting the documented dry run, the workflow still executes gh issue create for every matching post and files real issues in the hub repository. Either skip issue creation and print the proposed bodies for dispatch runs, or stop advertising this input and dispatch path as a dry run so manual verification cannot create unintended issues.
Useful? React with 👍 / 👎.
| title="$(fm_field "$f" title)" | ||
| date="$(fm_field "$f" date)" | ||
| description="$(fm_field "$f" description)" |
There was a problem hiding this comment.
Read dispatched metadata from the requested head
When workflow_dispatch supplies a head_sha other than the checked-out workflow ref, the diff correctly selects paths from that requested head but these calls read each file from the checkout working tree instead. A post changed again after the requested head therefore produces metadata from the wrong revision, while a post absent at the checkout tip makes awk fail; extract the front matter from $head or explicitly check out the requested head before processing.
Useful? React with 👍 / 👎.
Review:
|
Round-3 review: the dispatch inputs claimed dry-run semantics but filed real issues. dry_run input (default true) now prints what would be filed; pass dry_run=false to file for real from a manual range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
|
Review round 3 addressed: workflow_dispatch now defaults to a true dry run (dry_run=false files for real). On the AGENTS.md reference the reviewer flagged: the publishing-rule gotcha lands in #39 (feature/blog-hub-spoke-intro), so the reference resolves once both PRs are on develop — merge in either order, they're complementary. Block-scalar front matter noted as a latent limitation; current posts all use single-line scalars. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88d3f1a9c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| # Let a genuinely failing diff (bad ref, shallow history) fail the | ||
| # step loudly; only the "no .md files matched" case is a normal no-op. | ||
| diff_out="$(git diff --name-only --diff-filter=AM "$base" "$head" -- '_blog_posts/')" |
There was a problem hiding this comment.
Skip unpublished posts before notifying the hub
When a main-branch change adds or edits a document with published: false, this path-only diff includes it even though the production pipeline runs Jekyll without --unpublished, so Jekyll does not emit the page. The workflow consequently files an issue claiming the post was published and supplies a canonical URL that returns 404; parse the front matter and skip documents that are not publishable before creating issues.
Useful? React with 👍 / 👎.
Review:
|
- Parse front matter with PyYAML and re-serialize the snippet with yaml.safe_dump: kills the escape/quoting corruption class (embedded quotes, colons, block scalars) the shell munging couldn't handle - Skip posts with published: false (Jekyll won't emit them; the old path filed issues with 404 canonical URLs) - Read post content from the head revision via git show, not the checkout tree — dispatch runs against older ranges now read the right bytes - Add --no-renames so a renamed post surfaces as an Add at its new path instead of vanishing from --diff-filter=AM - Remove the concurrency group: GitHub replaces queued runs, so queueing could drop a middle push's diff range permanently; a rare duplicate issue is cheaper than a permanently missed post Verified: YAML parses, bash -n clean, embedded python ast-parses, and the parser round-trips all 7 existing posts with correct quoting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e1d2f98bd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| # One fetch of open sync issues; exact-title dedup below instead of | ||
| # relying on GitHub search tokenization/index lag. | ||
| open_titles="$(gh issue list -R "$HUB_REPO" --label blog-hub-sync --state open --limit 200 --json title --jq '.[].title')" |
There was a problem hiding this comment.
Distinguish updates from first-time publication
When an already indexed post is edited after its original sync issue has been closed, the modified path is processed again, but deduplication checks only open issues and the generated body instructs the editor to insert a new entry at the top. Following that instruction creates a duplicate hub entry for routine post corrections; check the hub's existing entries or otherwise distinguish first publication from updates.
Useful? React with 👍 / 👎.
|
|
||
| for f in "${files[@]}"; do | ||
| slug="$(basename "$f" .md)" | ||
| url="${SITE_BASE}/${slug}/" |
There was a problem hiding this comment.
Honor front-matter permalinks in generated URLs
When a collection document overrides its route with a front-matter permalink, Jekyll publishes it at that override, but this always constructs /blog/<filename>/. The resulting issue and ready-to-paste hub entry then contain a canonical URL that can return 404; derive the effective URL from the parsed front matter rather than always using the basename.
Useful? React with 👍 / 👎.
- Diff with --name-status: A files a "Index new" issue as before, M files a "Sync updated" issue instructing editors to update the existing hub entry rather than insert a duplicate - Dedup checks both issue flavors so an edit while the original issue is still open files nothing - Honor front-matter permalink overrides when deriving the canonical URL; default remains /blog/<basename>/ from the collection config Verified: yaml/bash/ast clean; functional tests for new mode, updated mode, permalink override, and published: false skip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
Sonar S8544 (unpinned dependency) and S8541 (sdist setup-script execution): the pip fallback now installs pyyaml==6.0.2 with --only-binary :all:. The fallback still only fires if the runner image ever drops its preinstalled PyYAML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd
|



Closes #41. Spoke side of DollhouseMCP/DollhouseResearch-website#39 (revised event-driven design, no auto-PR).
What it does
On push to
maintouching_blog_posts/**:title,date,descriptionfrom front matter (commit date as fallback)blog-hub-sync(label already created), containing a ready-to-paste_data/blog_hub.ymlentry and the canonical/blog/<slug>/URLThe same-session manual rule in AGENTS.md remains the fast path; this is the backstop that removes staleness risk.
Setup needed after merge (Mick)
Create secret
BLOG_HUB_NOTIFY_TOKENon this repo: fine-grained PAT, resource owner DollhouseMCP, repository access onlyDollhouseResearch-website, permissions Issues: Read and write. Until it exists the workflow no-ops with a step-summary notice (VisiDelta-style) instead of failing the push.Verification
bash -nmain+_blog_posts/**paths, so PR branches never run it🤖 Generated with Claude Code
https://claude.ai/code/session_018y4M47JPR3dawoDUpCTbtd