Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 219 additions & 31 deletions .github/workflows/news-generation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,56 @@ jobs:
fi
fi

- name: Generate news articles
- name: Check for recent agentic workflow activity
if: steps.check-updates.outputs.should_generate == 'true'
id: check-agentic
run: |
echo "πŸ€– Checking for recent agentic workflow activity..."

if [ -f "news/metadata/workflow-state.json" ]; then
# Check if agentic workflows generated content recently (< 2 hours)
LAST_AGENTIC=$(jq -r '.lastUpdate // empty' news/metadata/workflow-state.json)

if [ -n "$LAST_AGENTIC" ]; then
# Safely parse lastUpdate and guard against invalid or future timestamps
if PARSED_AGENTIC_TS=$(date -d "$LAST_AGENTIC" +%s 2>/dev/null); then
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date parsing uses date -d which may not be portable across different Unix systems (GNU date vs BSD date). Consider using a more portable approach or adding a fallback for BSD-based systems to ensure the workflow runs correctly on different runner environments.

Suggested change
if [ -f "news/metadata/workflow-state.json" ]; then
# Check if agentic workflows generated content recently (< 2 hours)
LAST_AGENTIC=$(jq -r '.lastUpdate // empty' news/metadata/workflow-state.json)
if [ -n "$LAST_AGENTIC" ]; then
# Safely parse lastUpdate and guard against invalid or future timestamps
if PARSED_AGENTIC_TS=$(date -d "$LAST_AGENTIC" +%s 2>/dev/null); then
# Portable timestamp parsing: try GNU date, then BSD date, then Python
parse_timestamp() {
_input="$1"
# Try GNU date (-d)
if _ts=$(date -d "$_input" +%s 2>/dev/null); then
printf '%s\n' "$_ts"
return 0
fi
# Try BSD date (-j -f) for common ISO8601 without fractional seconds
if _ts=$(date -j -f '%Y-%m-%dT%H:%M:%S%z' "$_input" +%s 2>/dev/null); then
printf '%s\n' "$_ts"
return 0
fi
# Fallback: use Python 3 to parse a few common ISO8601 variants
if command -v python3 >/dev/null 2>&1; then
if _ts=$(python3 - <<'PY' "$_input" 2>/dev/null
import sys, datetime
s = sys.argv[1]
formats = [
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S.%f%z",
"%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%dT%H:%M:%S.%fZ",
]
for fmt in formats:
try:
if fmt.endswith("Z"):
# Treat trailing Z as UTC
dt = datetime.datetime.strptime(s, fmt)
dt = dt.replace(tzinfo=datetime.timezone.utc)
else:
dt = datetime.datetime.strptime(s, fmt)
print(int(dt.timestamp()))
sys.exit(0)
except Exception:
continue
sys.exit(1)
PY
); then
printf '%s\n' "$_ts"
return 0
fi
fi
return 1
}
if [ -f "news/metadata/workflow-state.json" ]; then
# Check if agentic workflows generated content recently (< 2 hours)
LAST_AGENTIC=$(jq -r '.lastUpdate // empty' news/metadata/workflow-state.json)
if [ -n "$LAST_AGENTIC" ]; then
# Safely parse lastUpdate and guard against invalid or future timestamps
if PARSED_AGENTIC_TS=$(parse_timestamp "$LAST_AGENTIC"); then

Copilot uses AI. Check for mistakes.
NOW_TS=$(date +%s)

# Check for future timestamp (clock skew or invalid data)
if [ "$PARSED_AGENTIC_TS" -gt "$NOW_TS" ]; then
# Future timestamp - treat as invalid, not recent
echo "agentic_recent=false" >> $GITHUB_OUTPUT
echo "⚠️ Future timestamp in workflow state (clock skew?), proceeding with traditional workflow"
else
# Valid past timestamp - calculate hours ago
HOURS_AGO=$(( (NOW_TS - PARSED_AGENTIC_TS) / 3600 ))

if [ "$HOURS_AGO" -lt 2 ]; then
echo "agentic_recent=true" >> $GITHUB_OUTPUT
echo "βœ… Agentic workflows active (${HOURS_AGO}h ago), skipping traditional workflow"
else
echo "agentic_recent=false" >> $GITHUB_OUTPUT
echo "ℹ️ No recent agentic activity (${HOURS_AGO}h ago), proceeding with traditional workflow"
fi
fi
else
# Unparseable timestamp
echo "agentic_recent=false" >> $GITHUB_OUTPUT
echo "⚠️ Unparseable lastUpdate timestamp in workflow state, proceeding with traditional workflow"
fi
else
echo "agentic_recent=false" >> $GITHUB_OUTPUT
echo "ℹ️ Invalid lastUpdate in workflow state, proceeding with traditional workflow"
fi
else
echo "agentic_recent=false" >> $GITHUB_OUTPUT
echo "ℹ️ No agentic workflow state found, proceeding with traditional workflow"
fi

- name: Generate news articles
if: |
steps.check-updates.outputs.should_generate == 'true' &&
steps.check-agentic.outputs.agentic_recent != 'true'
id: generate
env:
ARTICLE_TYPES: ${{ github.event.inputs.article_types || '' }}
Expand Down Expand Up @@ -181,36 +229,97 @@ jobs:
# Create metadata directory
mkdir -p news/metadata

# Run news generation script
if [ -f "scripts/generate-news-enhanced.js" ]; then
node scripts/generate-news-enhanced.js --types="$ARTICLE_TYPES" --languages="$LANG_ARG"
# Check script exists before running
if [ ! -f "scripts/generate-news-enhanced.js" ]; then
echo "❌ CRITICAL ERROR: scripts/generate-news-enhanced.js not found"

# Check results
if [ -f "news/metadata/generation-result.json" ]; then
GENERATED=$(jq -r '.generated' news/metadata/generation-result.json)
ERRORS=$(jq -r '.errors' news/metadata/generation-result.json)

echo "generated=$GENERATED" >> $GITHUB_OUTPUT
echo "errors=$ERRORS" >> $GITHUB_OUTPUT
echo "βœ… Generated $GENERATED articles with $ERRORS errors"
# Log structured error
cat > news/metadata/errors.json <<EOF
{
"lastError": {
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"workflow": "news-generation.yml",
"errorType": "script_missing",
"message": "scripts/generate-news-enhanced.js not found",
"severity": "critical",
"retryable": false
},
"errorHistory": [
{
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"workflow": "news-generation.yml",
"errorType": "script_missing",
"message": "scripts/generate-news-enhanced.js not found",
"severity": "critical",
"retryable": false
}
]
}
EOF
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error JSON structure is duplicated between lastError and errorHistory with identical content. This duplication could lead to maintenance issues if the schema needs updating. Consider using jq to construct the error object once and reuse it, or create the errorHistory by reading from lastError.

Suggested change
cat > news/metadata/errors.json <<EOF
{
"lastError": {
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"workflow": "news-generation.yml",
"errorType": "script_missing",
"message": "scripts/generate-news-enhanced.js not found",
"severity": "critical",
"retryable": false
},
"errorHistory": [
{
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"workflow": "news-generation.yml",
"errorType": "script_missing",
"message": "scripts/generate-news-enhanced.js not found",
"severity": "critical",
"retryable": false
}
]
}
EOF
ERROR_TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
jq -n --arg ts "$ERROR_TIMESTAMP" '
{
lastError: {
timestamp: $ts,
workflow: "news-generation.yml",
errorType: "script_missing",
message: "scripts/generate-news-enhanced.js not found",
severity: "critical",
retryable: false
}
}
| . + { errorHistory: [ .lastError ] }
' > news/metadata/errors.json

Copilot uses AI. Check for mistakes.

echo "generated=0" >> $GITHUB_OUTPUT
echo "errors=1" >> $GITHUB_OUTPUT
exit 1
fi

# Run news generation script with error capture
set +e
node scripts/generate-news-enhanced.js --types="$ARTICLE_TYPES" --languages="$LANG_ARG" 2>&1 | tee generation.log
EXIT_CODE=$?
set -e

# Handle script execution results
if [ $EXIT_CODE -ne 0 ]; then
echo "❌ Generation failed with exit code $EXIT_CODE"

# Detect error type from logs
if grep -q "MCP.*timeout" generation.log 2>/dev/null || grep -q "MCP.*unavailable" generation.log 2>/dev/null; then
ERROR_TYPE="mcp_unavailable"
ERROR_SEVERITY="warning"
ERROR_MSG="riksdag-regering-mcp server timeout or unavailable"
else
echo "generated=0" >> $GITHUB_OUTPUT
echo "errors=1" >> $GITHUB_OUTPUT
echo "❌ Generation failed - no result file"
ERROR_TYPE="script_failure"
ERROR_SEVERITY="error"
ERROR_MSG="News generation script failed with exit code $EXIT_CODE"
fi
Comment on lines 305 to 321
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCP failure classification is likely incorrect for real timeouts: the workflow only greps for MCP.*timeout/MCP.*unavailable, but the MCP client throws errors like MCP request failed: ${error.message} (often AbortError/"aborted") which won’t match those patterns. This will misclassify MCP outages/timeouts as script_failure (severity=error) instead of mcp_unavailable (warning). Expand the detection (e.g., case-insensitive grep for AbortError|aborted|mcp request failed|econnrefused|network) or have the generator emit an explicit sentinel on MCP connectivity failures.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 739310b - MCP error detection now includes all actual error patterns from scripts/mcp-client.js:

grep -qi "MCP request failed" || \
grep -qi "AbortError" || \
grep -qi "aborted" || \
grep -qi "ECONNREFUSED" || \
grep -qi "network.*error"

This correctly classifies MCP outages as mcp_unavailable (warning) instead of script_failure (error).

else
echo "⚠️ News generation script not found"
echo "πŸ“ Creating placeholder metadata"

# Create placeholder
echo '{"timestamp": "'${{ steps.check-updates.outputs.current_time }}'", "generated": 0, "note": "Script not implemented"}' > news/metadata/last-generation.json
# Log structured error
cat > news/metadata/errors.json <<EOF
{
"lastError": {
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"workflow": "news-generation.yml",
"errorType": "$ERROR_TYPE",
"message": "$ERROR_MSG",
"severity": "$ERROR_SEVERITY",
"retryable": true
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error logging for script execution failures (lines 287-298) is missing the errorHistory array field, which is inconsistent with the documented schema in news/metadata/README.md and the error logging for script_missing errors (lines 247-256). The errorHistory array should be included in all errors.json writes to maintain schema consistency and avoid breaking tooling that expects this field to exist.

Suggested change
}
},
"errorHistory": []

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a94e2ef - added errorHistory array to script execution error logging (lines 297-306), now consistent with script_missing error logging

}
EOF

echo "generated=0" >> $GITHUB_OUTPUT
echo "errors=1" >> $GITHUB_OUTPUT
exit 1
fi

# Check results
if [ -f "news/metadata/generation-result.json" ]; then
GENERATED=$(jq -r '.generated' news/metadata/generation-result.json)
ERRORS=$(jq -r '.errors' news/metadata/generation-result.json)

echo "generated=$GENERATED" >> $GITHUB_OUTPUT
echo "errors=$ERRORS" >> $GITHUB_OUTPUT
echo "βœ… Generated $GENERATED articles with $ERRORS errors"
else
echo "generated=0" >> $GITHUB_OUTPUT
echo "errors=0" >> $GITHUB_OUTPUT
echo "ℹ️ No articles generated (no new content available)"
fi

- name: Regenerate news indexes
if: steps.check-updates.outputs.should_generate == 'true'
if: |
steps.check-updates.outputs.should_generate == 'true' &&
steps.check-agentic.outputs.agentic_recent != 'true'
id: indexes
run: |
echo "πŸ—‚οΈ Regenerating news index files..."
Expand Down Expand Up @@ -276,17 +385,23 @@ jobs:
echo "βœ… Timestamp updated"

- name: Commit timestamp update (when no articles generated)
if: steps.check-updates.outputs.should_generate == 'true' && steps.generate.outputs.generated == '0'
if: |
steps.check-updates.outputs.should_generate == 'true' &&
steps.check-agentic.outputs.agentic_recent != 'true' &&
steps.generate.outputs.generated == '0'
run: |
echo "⏰ Committing timestamp update (0 articles generated)..."
echo "ℹ️ This prevents infinite retry loops when no new content is available"

git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add news/metadata/last-generation.json
if git diff --staged --quiet; then
echo "ℹ️ No timestamp changes to commit"
else
git commit -m "chore: Update news generation timestamp (0 articles generated)"
git commit -m "chore: Update news generation timestamp (no new content)"
git push
echo "βœ… Timestamp committed to main branch"
echo "βœ… Timestamp committed - prevents retry loop"
fi

- name: Validate generated articles
Expand Down Expand Up @@ -338,27 +453,30 @@ jobs:
echo "*Workflow execution: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*" >> $GITHUB_STEP_SUMMARY

- name: Create PR with generated articles
if: steps.generate.outputs.generated != '0'
if: |
success() &&
steps.generate.outcome == 'success' &&
steps.generate.outputs.generated != '0' &&
steps.generate.outputs.generated != ''
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'news: Automated news generation - ${{ steps.check-updates.outputs.current_time }}'
title: 'πŸ“° Automated News Update - ${{ steps.check-updates.outputs.current_time }}'
commit-message: 'news: ${{ steps.generate.outputs.generated }} articles - ${{ steps.check-updates.outputs.current_time }}'
title: 'πŸ“° ${{ steps.generate.outputs.generated }} News Articles - ${{ steps.check-updates.outputs.current_time }}'
body: |
## Automated News Generation

This PR contains automatically generated news articles from riksdag-regering-mcp data.

### Summary
- **Articles Generated**: ${{ steps.generate.outputs.generated }}
- **Article Types**: ${{ github.event.inputs.article_types || 'week-ahead (default)' }}
- **Languages**: ${{ github.event.inputs.languages || 'en,sv (default)' }}
- **Errors**: ${{ steps.generate.outputs.errors }}
- **News Indexes**: ${{ steps.indexes.outputs.updated == 'true' && 'Regenerated (all 14 languages)' || 'Not updated' }}
- **Sitemap Updated**: ${{ steps.sitemap.outputs.updated }}
- **Generation Time**: ${{ steps.check-updates.outputs.current_time }}

### Article Types
${{ github.event.inputs.article_types || 'week-ahead (default)' }}

### Data Sources
- **riksdag-regering-mcp**: Swedish Parliament and Government data
- **32 specialized tools**: Documents, MPs, votes, speeches, calendar events
Expand All @@ -368,7 +486,7 @@ jobs:
- [x] News indexes regenerated (14 languages)
- [x] Sitemap updated
- [ ] Editorial review recommended
- [ ] Multi-language support (EN/SV)
- [ ] Multi-language support verification

### References
- Workflow: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
Expand All @@ -388,3 +506,73 @@ jobs:
automated-news
news-generation
needs-editorial-review

- name: Notify on critical failure
if: failure() && steps.generate.outcome == 'failure'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const fs = require('fs');

// Check if errors.json exists and has critical error
let errorData = null;
try {
if (fs.existsSync('news/metadata/errors.json')) {
const errorContent = fs.readFileSync('news/metadata/errors.json', 'utf8');
errorData = JSON.parse(errorContent);
}
} catch (err) {
console.log('Could not read errors.json:', err.message);
}

// Only create issue comment for critical errors
if (errorData && errorData.lastError && errorData.lastError.severity === 'critical') {
// Try to find an open issue about news generation failures
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'component:news-generation,type:bug',
per_page: 1
});

let issueNumber = null;
if (issues.data.length > 0) {
issueNumber = issues.data[0].number;
}

const commentBody = `🚨 **Critical Failure in News Generation Workflow**

**Error Details:**
- **Type**: ${errorData.lastError.errorType}
- **Message**: ${errorData.lastError.message}
- **Severity**: ${errorData.lastError.severity}
- **Timestamp**: ${errorData.lastError.timestamp}
- **Retryable**: ${errorData.lastError.retryable ? 'Yes' : 'No'}

**Workflow Information:**
- **Workflow**: ${context.workflow}
- **Run ID**: ${context.runId}
- **Run URL**: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}

**Action Required:**
${errorData.lastError.errorType === 'script_missing' ?
'⚠️ Critical script missing. This requires immediate attention from maintainers.' :
'⚠️ Workflow failure detected. Please investigate and resolve.'}`;

if (issueNumber) {
// Comment on existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: commentBody
});
console.log(`Added comment to issue #${issueNumber}`);
} else {
console.log('No open news generation bug issue found - skipping notification');
console.log(commentBody);
}
} else {
console.log('Non-critical error or no error data - skipping notification');
}
Loading
Loading