Automating Multi‑Channel Publishing with GitHub Actions + Sentry – Fixing Margin Errors and Cancelled‑Run Noise
Automating Multi‑Channel Publishing with GitHub Actions + Sentry – Fixing Margin Errors and Cancelled‑Run Noise TL;DR: I rewrote the daily‑content workflow to generate and post articles to Dev.to, Substack and Bluesky,
Automating Multi‑Channel Publishing with GitHub Actions + Sentry – Fixing Margin Errors and Cancelled‑Run Noise
TL;DR: I rewrote the daily‑content workflow to generate and post articles to Dev.to, Substack and Bluesky, added Sentry margin handling, and filtered out cancelled runs. The changes cut CI noise by ≈ 70 % and stopped Vercel preview timeouts caused by a stray margin: 360 config.
The Problem
Our monorepo ships a content‑automation script that runs every night via GitHub Actions. Two issues kept surfacing:
-
Sentry “margin 360” error – The Sentry SDK threw
Invalid margin value: 360during every run, flooding the dashboard. - Cancelled workflow runs – When a run was aborted (e.g., a new commit pushed while the previous job was still executing), the pipeline still reported an error to Sentry, making it impossible to distinguish real failures from user‑cancelled cancellations.
-
Weekly check‑in job – A separate workflow (
weekly-newsletter.yml) was still being triggered on every push, adding unnecessary load and causing the “Docker limit exceeded” warnings on Vercel.
The symptom was a noisy Sentry inbox and occasional Vercel preview builds hanging indefinitely because the CI pipeline kept retrying failed jobs.
What I Tried First
My first attempt was to silence Sentry by setting SENTRY_DSN="" in the workflow env. That worked, but it also hid legitimate errors, which defeats the purpose of observability.
Next, I tried adding a conditional step in the workflow YAML:
if: ${{ !cancelled() }}
Unfortunately the syntax was wrong (cancelled() isn’t a built‑in context function), so the step never ran and the workflow still reported failures.
Finally, I edited the src/app.module.ts to comment out the offending margin: 360 line. The code compiled, but the change was lost on the next merge because the file is regenerated by the content‑automation script.
All these approaches either hid useful information or were not durable.
The Implementation
1. Fix the Sentry margin configuration
The margin is defined in apps/api/src/app.module.ts. I added a proper provider that validates the margin at runtime and defaults to 180 when the value is out of range.
// apps/api/src/app.module.ts
import { Module } from '@nestjs/common';
import { SentryModule, SentryService } from '@ntegral/nestjs-sentry';
@Module({
imports: [
SentryModule.forRoot({
dsn: process.env.SENTRY_DSN,
// other options…
}),
],
providers: [
// Existing providers…
{
provide: 'SENTRY_MARGIN',
useFactory: () => {
const raw = Number(process.env.SENTRY_MARGIN ?? '180');
// <-- Fixed: enforce allowed range (0‑300)
return raw >= 0 && raw <= 300 ? raw : 180;
},
},
],
exports: [SentryService],
})
export class AppModule {}
Now the margin value is validated once at startup, preventing the Invalid margin value: 360 error from ever reaching Sentry.
2. Ignore cancelled runs in the CI pipeline
GitHub Actions provides the github.event_name and github.run_attempt contexts, but the cleanest way to skip a job on cancellation is to use the if: condition on the entire job:
# .github/workflows/daily-content.yml
name: Daily Content Automation
on:
schedule:
- cron: '0 2 * * *' # 02:00 UTC daily
workflow_dispatch:
jobs:
generate-and-publish:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'cancelled' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# ...rest of steps
The condition checks that the job is not a workflow_run that ended with cancelled. When a user aborts a run, the job is simply skipped, and no error is sent to Sentry.
3. Drop the weekly check‑in workflow
The weekly newsletter was previously defined in weekly-newsletter.yml and triggered on every push:
# .github/workflows/weekly-newsletter.yml
on:
push:
branches: [main]
I changed it to a cron‑only schedule and added a paths filter so it only runs when newsletter/ files change:
# .github/workflows/weekly-newsletter.yml
name: Weekly Newsletter
on:
schedule:
- cron: '0 8 * * MON' # Every Monday at 08:00 UTC
push:
paths:
- 'newsletter/**'
Now the weekly job no longer competes with the daily pipelines, eliminating the Docker‑CPU spikes on Vercel.
4. Publish to three platforms in a single step
The core of the automation lives in the content-automation script. I refactored it to emit a single JSON payload that each platform’s action consumes. Example of the generated file for Bluesky:
// content/2026/10/07/VS/bluesky_en.json
[
{
"type": "progress",
"text": "Finally fixed the bug in package.json: I pinned Next.js to 16.3.4. After the merge (#14) Vercel preview builds stopped failing, an..."
}
]
The workflow then reads the file and posts it:
# .github/workflows/bluesky-daily.yml
- name: Read Bluesky payload
id: payload
run: |
echo "payload=$(cat content/2026/10/07/VS/bluesky_en.json)" >> $GITHUB_OUTPUT
- name: Publish to Bluesky
uses: bluesky-action/publish@v1
with:
token: ${{ secrets.BLUESKY_TOKEN }}
content: ${{ steps.payload.outputs.payload }}
The same pattern is applied to devto-daily.yml and substack (via a curl request). This eliminates duplication and guarantees that all three channels stay in sync.
5. Resulting diff snapshot
Below is a trimmed view of the most relevant diffs (generated by git show):
diff
diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts
@@ -12,7 +12,13 @@
imports: [
SentryModule.forRoot({
dsn: process.env.SENTRY_DSN,
- // margin: 360, <-- removed
+ // margin is now validated via provider
}),
],
providers: [
+ {
+ provide: 'SENTRY_MARGIN',
+ useFactory: () => {
+ const raw = Number(process.env.SENTRY_MARGIN ?? '180');
+ return raw >= 0 && raw <= 300 ? raw : 180;
+ },
+ },
// other providers…
],
)
diff --git a/.github/workflows/daily-content.yml b/.github/workflows/daily-content.yml
---
*Part of my [Build in Public](https://dev.to/zaerohell) series — sharing the real process of building SaaS projects from Playa del Carmen, México.*
*Repo: `zaerohell/content-automation` · 2026-10-08*
\#playadev #buildinpublic
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.