Skip to content

Commit 37a1aee

Browse files
authored
fix syndication spam + add cache diagnostics [skip netlify]
1 parent 3efe239 commit 37a1aee

4 files changed

Lines changed: 168 additions & 1 deletion

File tree

.github/scripts/inspect-syndication-cache.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,51 @@ function getIncompleteItems(collection = {}) {
2929
}));
3030
}
3131

32+
function printVerboseSection(label, collection = {}) {
33+
const entries = Object.entries(collection);
34+
if (entries.length === 0) {
35+
console.log(`\n=== ${label} (0 entries) ===`);
36+
return;
37+
}
38+
39+
console.log(`\n=== ${label} (${entries.length} entries) ===`);
40+
41+
// Sort by most-recently updated first for readability
42+
const sorted = entries.sort(([, a], [, b]) => {
43+
const aTime = a?.lastUpdated || a?.firstAttempt || "";
44+
const bTime = b?.lastUpdated || b?.firstAttempt || "";
45+
return bTime.localeCompare(aTime);
46+
});
47+
48+
for (const [itemId, itemStatus] of sorted) {
49+
const platforms = itemStatus?.platforms || {};
50+
const platformNames = Object.keys(platforms).sort();
51+
const allSuccess = platformNames.length > 0 && platformNames.every((p) => platforms[p]?.success);
52+
const statusIcon = allSuccess ? "✅" : "⚠️ ";
53+
console.log(`\n ${statusIcon} ${itemId}`);
54+
console.log(` firstAttempt : ${itemStatus?.firstAttempt || "(none)"}`);
55+
console.log(` lastUpdated : ${itemStatus?.lastUpdated || "(none)"}`);
56+
if (platformNames.length === 0) {
57+
console.log(` platforms : (none)`);
58+
} else {
59+
for (const platform of platformNames) {
60+
const p = platforms[platform];
61+
const icon = p?.success ? "✅" : "❌";
62+
const flags = [
63+
p?.seeded ? "seeded" : null,
64+
p?.markedManually ? "markedManually" : null,
65+
].filter(Boolean).join(", ");
66+
const flagStr = flags ? ` [${flags}]` : "";
67+
const errStr = p?.error ? ` -- ERROR: ${p.error}` : "";
68+
console.log(` ${icon} ${platform.padEnd(10)} @ ${p?.timestamp || "(no timestamp)"}${flagStr}${errStr}`);
69+
}
70+
}
71+
}
72+
}
73+
3274
async function main() {
33-
const samplePostId = process.argv[2] || DEFAULT_SAMPLE_POST_ID;
75+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
76+
const samplePostId = process.argv.find((a) => a.startsWith("--sample="))?.slice(9) || DEFAULT_SAMPLE_POST_ID;
3477

3578
try {
3679
const content = await fs.readFile(CACHE_FILE, "utf8");
@@ -69,6 +112,18 @@ async function main() {
69112
} else {
70113
console.log(`sample_post_missing=${samplePostId}`);
71114
}
115+
116+
if (verbose) {
117+
console.log("\n========================================");
118+
console.log("VERBOSE CACHE DUMP");
119+
console.log("========================================");
120+
console.log(`initialized : ${cache.initialized || "(unknown)"}`);
121+
printVerboseSection("POSTS", cache.posts);
122+
printVerboseSection("LINKS", cache.links);
123+
console.log("\n========================================");
124+
console.log("END VERBOSE CACHE DUMP");
125+
console.log("========================================");
126+
}
72127
} catch (error) {
73128
console.log(`cache_file_missing=${CACHE_FILE}`);
74129
console.log(`cache_error=${error.message}`);
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Inspect Syndication Cache
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
branch:
7+
description: "Branch whose cache to inspect (defaults to main)"
8+
required: false
9+
default: "main"
10+
type: string
11+
verbose:
12+
description: "Dump full cache contents (every entry with per-platform details)"
13+
required: false
14+
default: true
15+
type: boolean
16+
17+
# Use the same concurrency group as the syndication workflow so this read-only
18+
# inspection cannot race with an active syndication or mark-all-sent run.
19+
concurrency:
20+
group: syndication-${{ github.event.inputs.branch || 'main' }}
21+
cancel-in-progress: false
22+
23+
jobs:
24+
inspect:
25+
runs-on: ubuntu-latest
26+
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v6
30+
with:
31+
ref: ${{ github.event.inputs.branch || 'main' }}
32+
33+
- name: Setup Node.js
34+
uses: actions/setup-node@v6
35+
with:
36+
node-version: "20"
37+
38+
- name: Resolve latest processed items cache key
39+
id: syndication-cache-latest
40+
env:
41+
GH_TOKEN: ${{ github.token }}
42+
run: |
43+
target_branch="${{ github.event.inputs.branch || 'main' }}"
44+
key_prefix="syndication-cache-${target_branch}-"
45+
latest_key=$(curl -fsSL -G \
46+
-H "Authorization: Bearer $GH_TOKEN" \
47+
-H "Accept: application/vnd.github+json" \
48+
--data-urlencode "per_page=100" \
49+
--data-urlencode "key=${key_prefix}" \
50+
--data-urlencode "ref=refs/heads/${target_branch}" \
51+
"${{ github.api_url }}/repos/${{ github.repository }}/actions/caches" | jq -r '.actions_caches | sort_by(.created_at) | last | .key // empty')
52+
53+
if [ -n "$latest_key" ]; then
54+
echo "latest_key=$latest_key" >> "$GITHUB_OUTPUT"
55+
echo "Resolved latest cache key: $latest_key"
56+
else
57+
echo "latest_key=${key_prefix}" >> "$GITHUB_OUTPUT"
58+
echo "No existing cache key found for prefix: $key_prefix"
59+
fi
60+
61+
- name: Restore processed items cache
62+
id: syndication-cache
63+
uses: actions/cache/restore@v5
64+
with:
65+
path: .github/cache
66+
key: ${{ steps.syndication-cache-latest.outputs.latest_key }}
67+
restore-keys: |
68+
syndication-cache-${{ github.event.inputs.branch || 'main' }}-
69+
70+
- name: Inspect cache
71+
run: |
72+
args=""
73+
if [ "${{ github.event.inputs.verbose }}" = "true" ]; then
74+
args="--verbose"
75+
fi
76+
node .github/scripts/inspect-syndication-cache.js $args
77+
78+
- name: Show all available cache keys for this branch
79+
env:
80+
GH_TOKEN: ${{ github.token }}
81+
run: |
82+
target_branch="${{ github.event.inputs.branch || 'main' }}"
83+
key_prefix="syndication-cache-${target_branch}-"
84+
echo "All cached syndication-cache keys for branch '${target_branch}':"
85+
curl -fsSL -G \
86+
-H "Authorization: Bearer $GH_TOKEN" \
87+
-H "Accept: application/vnd.github+json" \
88+
--data-urlencode "per_page=100" \
89+
--data-urlencode "key=${key_prefix}" \
90+
--data-urlencode "ref=refs/heads/${target_branch}" \
91+
"${{ github.api_url }}/repos/${{ github.repository }}/actions/caches" \
92+
| jq -r '.actions_caches[] | "\(.created_at) \(.size_in_bytes)B \(.key)"' \
93+
| sort -r

.github/workflows/mark-all-sent.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ jobs:
7676
restore-keys: |
7777
syndication-cache-${{ github.ref_name }}-
7878
79+
# Warn when run on a non-main branch so the operator knows they are
80+
# updating a branch-scoped cache, not the production (main) cache.
81+
- name: Warn if running on non-main branch
82+
if: github.ref_name != 'main'
83+
run: |
84+
echo "⚠️ This workflow is running on branch '${{ github.ref_name }}', not 'main'."
85+
echo " The syndication cache is scoped per-branch, so this update will only"
86+
echo " affect the cache for '${{ github.ref_name }}'."
87+
echo " To update the production cache, re-run this workflow on 'main'."
88+
7989
- name: Inspect cache before update
8090
run: node .github/scripts/inspect-syndication-cache.js
8191

.github/workflows/syndicate-content.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ jobs:
115115
echo "BOOTSTRAP_CACHE_BEFORE=${{ github.event.inputs.bootstrap_cache_before || '' }}" >> $GITHUB_ENV
116116
echo "BOOTSTRAP_CACHE_CONTENT_TYPE=${{ github.event.inputs.content_type || 'both' }}" >> $GITHUB_ENV
117117
118+
# Safety guard: prevent accidental real posts when running on a non-main branch.
119+
# This can happen when workflow_dispatch is triggered on a feature or Copilot branch
120+
# that has no syndication cache — every feed item would appear "new" and get posted.
121+
- name: Enforce test mode on non-main branches
122+
if: github.ref_name != 'main'
123+
run: |
124+
echo "⚠️ Running on non-main branch ('${{ github.ref_name }}'). Forcing TEST_MODE=true to prevent real posts."
125+
echo "TEST_MODE=true" >> $GITHUB_ENV
126+
118127
# Restore cache before processing to avoid duplicates
119128
- name: Restore processed items cache
120129
id: syndication-cache

0 commit comments

Comments
 (0)