GitHub Actions Automation Reference
Overview
This document describes the GitHub Actions workflows that automate the content generation pipeline. Two primary workflows handle content scheduling and validation.
Workflows
1. Calendar Check (calendar-check.yml)
Purpose: Automatically scans editorial calendars and creates GitHub Issues for content due within the next 7 days.
Location: .github/workflows/calendar-check.yml
Triggers
on:
schedule:
# Run every Monday at 9am UTC (4am EST)
- cron: '0 9 * * 1'
workflow_dispatch:
inputs:
days_ahead:
description: 'Days to look ahead for due content'
required: false
default: '7'
type: string
create_issues:
description: 'Actually create issues (false = dry run)'
required: false
default: true
type: boolean
| Trigger | When | Notes |
|---|---|---|
schedule |
Monday 9:00 UTC | Automated weekly run |
workflow_dispatch |
Manual | For testing or ad-hoc runs |
Inputs (Manual Trigger)
| Input | Type | Default | Description |
|---|---|---|---|
days_ahead |
string | "7" |
Number of days to look ahead |
create_issues |
boolean | true |
Set false for dry run |
Permissions
permissions:
contents: read # Read calendar files
issues: write # Create issues
Workflow Steps
Step 1: Checkout Repository
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Step 2: Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
Step 3: Install Dependencies
- name: Install dependencies
run: npm install js-yaml
Step 4: Scan Calendars
The main logic reads all YAML files in the calendar/ directory:
function checkCalendarFile(filepath) {
const content = fs.readFileSync(filepath, 'utf8');
const cal = yaml.load(content);
if (!cal.months) return;
for (const [month, data] of Object.entries(cal.months)) {
if (!data.posts) continue;
for (const post of data.posts) {
// Skip already published or in-progress items
if (post.status === 'published' || post.status === 'in-progress') continue;
// Check if due_date falls within the lookahead window
if (post.due_date) {
const dueDate = new Date(post.due_date);
if (dueDate >= today && dueDate <= cutoff) {
dueItems.push({
...post,
month,
source: filepath
});
}
}
}
}
}
Step 5: Create Issues
For each due item, the workflow:
- Checks for Existing Issues
gh issue list --search "${item.title} in:title" --json number --limit 1 - Skips Duplicates
if (existing.length > 0) { console.log(`Issue already exists for: ${item.title} (#${existing[0].number})`); continue; } - Creates New Issue
gh issue create --title "[${type}] ${title}" --body "${body}" --label "${labels}"
Step 6: Generate Summary
- name: Summary
run: |
echo "## Calendar Check Summary" >> $GITHUB_STEP_SUMMARY
# ... summary generation
Issue Body Template
## Content Request from Editorial Calendar
**Type**: ${item.type}
**Title**: ${item.title}
**Due Date**: ${item.due_date}
**Publish Date**: ${item.publish_date || 'TBD'}
**Priority**: ${item.priority || 'medium'}
### Description
${item.description || 'No description provided'}
### Topics
${(item.topics || []).map(t => `- ${t}`).join('\n') || 'None specified'}
### Platforms
${(item.platforms || []).map(p => `- ${p}`).join('\n') || 'Blog only'}
---
### For Copilot
Use the following resources:
- Brand voice: `brands/zircote/brand.yml`
- Blog template: `brands/zircote/templates/blog-post.prompt.md`
- Schema: `schemas/blog.schema.yml`
Save draft to: `content/blog/drafts/${due_date}-${slug}.md`
---
*Auto-generated from calendar/${source}*
Labels Applied
| Label | When Applied |
|---|---|
content |
Always |
copilot |
Always (for GitHub Copilot visibility) |
blog |
When type: blog |
social |
When type: social |
video |
When type: video |
priority |
When priority: high or critical |
2. Content Validation (validate-content.yml)
Purpose: Validates content structure and formatting on pull requests.
Location: .github/workflows/validate-content.yml
Triggers
on:
pull_request:
paths:
- 'content/**'
- '_posts/**'
Validation Checks
- Frontmatter Validation
- Required fields present
- Date format (YYYY-MM-DD)
- Valid categories
- Schema Compliance
- Blog posts match
schemas/blog.schema.yml - Social posts match
schemas/social.schema.yml
- Blog posts match
- Link Validation
- Internal links resolve
- External links accessible
Manual Workflow Execution
Running Calendar Check Manually
# Default (7 days, create issues)
gh workflow run calendar-check.yml
# Custom lookahead
gh workflow run calendar-check.yml -f days_ahead=14
# Dry run (no issues created)
gh workflow run calendar-check.yml -f create_issues=false
# Combined
gh workflow run calendar-check.yml -f days_ahead=14 -f create_issues=false
Viewing Workflow Runs
# List recent runs
gh run list --workflow=calendar-check.yml
# View specific run
gh run view <run-id>
# View run logs
gh run view <run-id> --log
Viewing Created Issues
# List content issues
gh issue list --label content
# List by type
gh issue list --label blog
gh issue list --label social
# List priority items
gh issue list --label priority
Cron Schedule Reference
GitHub Actions Cron Syntax
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *
Current Schedule
- cron: '0 9 * * 1'
# │ │ │ │ └─ Monday
# │ │ │ └─── Every month
# │ │ └───── Every day of month
# │ └─────── 9:00 AM UTC
# └───────── 0 minutes
Alternative Schedules
# Twice weekly (Monday and Thursday)
- cron: '0 9 * * 1,4'
# Daily at 8 AM UTC
- cron: '0 8 * * *'
# Every 6 hours
- cron: '0 */6 * * *'
# First Monday of month only
- cron: '0 9 1-7 * 1'
Timezone Considerations
GitHub Actions cron runs in UTC. Convert as needed:
| UTC | EST | PST | Notes |
|---|---|---|---|
| 09:00 | 04:00 | 01:00 | Current schedule |
| 13:00 | 08:00 | 05:00 | Morning EST |
| 14:00 | 09:00 | 06:00 | Morning PST |
Secrets and Environment
Required Secrets
| Secret | Purpose | How to Set |
|---|---|---|
GITHUB_TOKEN |
Issue creation | Automatic (provided by Actions) |
Optional Secrets
| Secret | Purpose | When Needed |
|---|---|---|
SLACK_WEBHOOK |
Notifications | If Slack integration added |
DISCORD_WEBHOOK |
Notifications | If Discord integration added |
Error Handling
Common Errors
Calendar Parsing Errors
Error reading calendar/2026/q1.yml: Invalid YAML syntax
Fix: Validate YAML syntax with:
yq eval 'select(. != null)' calendar/2026/q1.yml
Issue Creation Failures
Failed to create issue for Title: error message
Causes:
- Rate limiting (wait and retry)
- Permission issues (check token)
- Network issues (retry workflow)
Duplicate Issue Detection
Issue already exists for: Title (#123)
Normal behavior: Workflow skips creating duplicates.
Debugging
# View full workflow logs
gh run view <run-id> --log
# Download log artifacts
gh run download <run-id>
# Re-run failed workflow
gh run rerun <run-id>
Extending the Workflows
Adding Slack Notifications
- name: Notify Slack
if: steps.check.outputs.count != '0'
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "📅 Content due this week: $ items"
}
env:
SLACK_WEBHOOK_URL: $
Adding Email Notifications
- name: Send Email
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: $
password: $
subject: Content Due This Week
to: you@example.com
from: GitHub Actions
body: "$ items due"
Custom Issue Assignees
Modify the issue creation to add assignees:
gh issue create \
--title "[${type}] ${title}" \
--body "${body}" \
--label "${labels}" \
--assignee "@me"
Priority-Based Assignees
const assignees = [];
if (item.priority === 'critical' || item.priority === 'high') {
assignees.push('zircote');
}
if (item.type === 'video') {
assignees.push('video-producer');
}
Monitoring
Workflow Status Badge
Add to README:
[](https://github.com/zircote/zircote.github.io/actions/workflows/calendar-check.yml)
Scheduled Run History
# View last 10 scheduled runs
gh run list --workflow=calendar-check.yml --limit=10 --event=schedule
Setting Up Alerts
Configure GitHub notification settings:
- Settings → Notifications → Actions
- Enable email for workflow failures
- Enable mobile push for critical workflows
Troubleshooting
Workflow Not Running
- Check schedule is correct
gh workflow view calendar-check.yml - Verify workflow is enabled
- Actions tab → workflow → Enable if disabled
- Check for repository limits
- Free tier: 2,000 minutes/month
- If exceeded, wait for reset or upgrade
Issues Not Being Created
- Check permissions
permissions: issues: write - Check token has correct scopes
GITHUB_TOKENshould work automatically
- Verify calendar has due content
# Dry run to check gh workflow run calendar-check.yml -f create_issues=false
Duplicate Issues Being Created
The workflow checks for duplicates by title. If duplicates appear:
- Issue title changed in calendar
- Old title issue still exists
- New title creates new issue
- Fix: Close duplicate issues manually
gh issue close <number> --comment "Duplicate of #<other>"