Fix Hashnode syndication recovery - #5520
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0002945bf
ℹ️ 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".
| entry = post.get(platform, {}) | ||
| return bool( | ||
| isinstance(entry, dict) | ||
| and entry.get("published") is False |
There was a problem hiding this comment.
Retry legacy draft entries without published flag
When recovering an older failed Hashnode run, this predicate still returns false for draft-only records that predate the new published: false field. For example, scripts/website/syndication-state.json already has metal-and-skins with a https://hashnode.com/draft/... URL and no published key, so --post-slug metal-and-skins --platforms hashnode will hit state.is_syndicated(...), fail this recovery check, and skip the very draft the new recovery path is meant to unblock. Treating draft URLs, or missing published on a draft URL, as retryable would cover those legacy records.
Useful? React with 👍 / 👎.
| if not retry_failed_draft: | ||
| print(f" [{adapter.name}] already syndicated; skipping.") | ||
| continue | ||
| print(f" [{adapter.name}] retrying explicitly recorded unpublished draft.") |
There was a problem hiding this comment.
Open the recorded Hashnode draft on recovery
This branch says it is retrying the recorded unpublished draft, but the selected draft URL is never passed to HashnodeAdapter; submit_draft() still starts from the dashboard and clicks Write, which edits whichever current draft Hashnode opens. In a real recovery after later browser runs have left a different draft in that slot, --post-slug can overwrite or publish that other draft instead of the selected post, so the recovery path should navigate to and verify the stored draft_url before editing.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR tightens the Hashnode browser-syndication flow so that a retained draft (when the publish dialog fails) is no longer persisted as a “successful” syndication, and adds an explicit recovery mode to reprocess a previously recorded unpublished draft without altering the normal candidate rotation.
Changes:
- Introduces
_completed_hashnode_result()to only persist Hashnode state after a public publish succeeds (otherwise fails the adapter run). - Adds
--post-slugto target a single eligible post, with special-case logic to allow retrying a previously persisted unpublished Hashnode draft. - Adds regression tests for “unpublished draft is not treated as completed” and for the explicit failed-draft retry predicate.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/website/syndicate_browser_posts.py | Stops persisting unpublished Hashnode drafts as completed, adds --post-slug selection + retry handling, and improves runner output messaging. |
| scripts/website/test_syndicate_browser_posts.py | Adds unit tests for Hashnode completion behavior and the failed-draft recovery predicate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| post = state.raw.get("posts", {}).get(slug, {}) | ||
| if not isinstance(post, dict): | ||
| return False | ||
| entry = post.get(platform, {}) | ||
| return bool( | ||
| isinstance(entry, dict) | ||
| and entry.get("published") is False | ||
| and entry.get("draft_url") | ||
| ) |
| print( | ||
| f"Post {candidate.slug} is not eligible on {today.isoformat()} " | ||
| f"with a {args.min_age_days}-day delay.", | ||
| file=sys.stderr, | ||
| ) |
Cloudflare Preview
|
What changed
--post-slugrecovery path for a known unpublished draft without changing the normal one-post rotation.Why
The browser flow could fail while setting tags, canonical URL, or clicking Publish, then record the retained draft URL in
syndication-state.json. Candidate selection treated any URL as complete, so the post was never retried and the health checker could not see the missing public publication.Validation
python3 scripts/website/test_syndicate_browser_posts.pypython3 scripts/website/test_syndication_health.pypython3 scripts/website/test_syndicate_blog_posts.pypython3 -m py_compile scripts/website/syndicate_browser_posts.py scripts/website/check_syndication_health.py scripts/website/test_syndicate_browser_posts.pyjq empty.