@gurupanguji

Skip Live Verification Failures Implementation Plan

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.


Task 1: Add a failing test for verification failure skipping

Files:

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()

Task 2: Change the verification helper to return a result

Files:

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
        if not args.dry_run and target_platform is None:
            mark_as_published(target_file)
            print(f"Marked {target_file.name} as published.")

Task 3: Verify the new skip behavior

Files:

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"