For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Keep the social publisher moving when a page is not ready, without marking skipped snippets as published.
Architecture: Change the verification helper to return a result instead of raising, then let the main loop decide whether to publish or skip. Add focused tests around the verification result and the queue behavior so the script never regresses back to aborting the whole run.
Tech Stack: Python 3, unittest, existing tests/test_publish_social.py harness, requests mocking.
Files:
Modify: tests/test_publish_social.py
Step 1: Add a test that a failed verification returns a skip result instead of raising
def test_wait_for_live_post_returns_skip_result_after_failed_attempts(self) -> None:
snippet = types.SimpleNamespace(
front_matter={
"canonical_url": "https://example.com/post",
"title": "Expected title",
}
)
original_get = self.ps.requests.get
self.addCleanup(setattr, self.ps.requests, "get", original_get)
def fake_get(*args, **kwargs):
return types.SimpleNamespace(status_code=404, text="not found")
self.ps.requests.get = fake_get
ok, message = self.ps.wait_for_live_post(
snippet,
attempts=2,
interval_seconds=0,
timeout_seconds=1,
)
self.assertFalse(ok)
self.assertIn("HTTP 404", message)
def test_main_skips_unverified_snippet_without_marking_published(self) -> None:
with mock.patch.object(self.ps, "wait_for_live_post", return_value=(False, "title mismatch")), \
mock.patch.object(self.ps, "publish_mastodon") as publish_mastodon, \
mock.patch.object(self.ps, "publish_bluesky") as publish_bluesky, \
mock.patch.object(self.ps, "publish_tumblr") as publish_tumblr, \
mock.patch.object(self.ps, "publish_linkedin") as publish_linkedin, \
mock.patch.object(self.ps, "publish_threads") as publish_threads, \
mock.patch.object(self.ps, "publish_instagram") as publish_instagram, \
mock.patch.object(self.ps, "publish_x") as publish_x, \
mock.patch.object(self.ps, "publish_nostr") as publish_nostr, \
mock.patch.object(self.ps, "mark_as_published") as mark_as_published, \
mock.patch.object(self.ps, "SNIPPETS_DIR", Path("/tmp/social-snippets-test")), \
mock.patch.object(self.ps.sys, "argv", ["publish_social.py", "--platform", "Mastodon"]):
self.ps.SNIPPETS_DIR.mkdir(parents=True, exist_ok=True)
snippet_path = self.ps.SNIPPETS_DIR / "2026-04-20-skip-test.md"
snippet_path.write_text(
"\n".join(
[
"---",
'title: "Skip Test"',
"date: 2026-04-20 12:00:00 +0000",
'canonical_url: "https://example.com/post"',
"---",
"",
"## Mastodon",
"Snippet: skip me",
]
)
+ "\n",
encoding="utf-8",
)
self.addCleanup(self._cleanup_temp_snippet_dir, snippet_path.parent)
self.ps.main()
publish_mastodon.assert_not_called()
publish_bluesky.assert_not_called()
publish_tumblr.assert_not_called()
publish_linkedin.assert_not_called()
publish_threads.assert_not_called()
publish_instagram.assert_not_called()
publish_x.assert_not_called()
publish_nostr.assert_not_called()
mark_as_published.assert_not_called()
Files:
Modify: scripts/publish_social.py
Step 1: Replace the SystemExit raise in wait_for_live_post() with a returned failure result
def wait_for_live_post(
snippet: SnippetData,
attempts: int,
interval_seconds: int,
timeout_seconds: int,
) -> tuple[bool, str]:
last_message = ""
for attempt in range(1, attempts + 1):
ok, message = verify_live_post(snippet, timeout_seconds)
last_message = message
print(f"[verify {attempt}/{attempts}] {message}", flush=True)
if ok:
return True, message
if attempt < attempts:
print(f"[verify {attempt}/{attempts}] Waiting {interval_seconds} seconds before next attempt...", flush=True)
time.sleep(interval_seconds)
return False, last_message or "Live post verification failed."
verified, message = wait_for_live_post(
snippet,
attempts=max(args.verify_attempts, 1),
interval_seconds=max(args.verify_interval_seconds, 0),
timeout_seconds=max(args.verify_timeout_seconds, 1),
)
if not verified:
print(f"Skipping {target_file.name}: {message}")
continue
mark_as_published() behind the successful publish path only if not args.dry_run and target_platform is None:
mark_as_published(target_file)
print(f"Marked {target_file.name} as published.")
Files:
scripts/publish_social.pyModify: tests/test_publish_social.py
python3 -m unittest tests/test_publish_social.py -v
# Change only the verification return shape and the loop branch.
python3 -m unittest tests/test_publish_social.py -v
python3 -m unittest tests/test_publish_social.py tests/test_validate_posts.py
git add scripts/publish_social.py tests/test_publish_social.py docs/superpowers/specs/2026-04-20-skip-live-verification-failures-design.md docs/superpowers/plans/2026-04-20-skip-live-verification-failures-implementation-plan.md
git commit -m "fix: skip social publishing when live verification fails"