Replace daily standups with a Python script and GitHub Actions workflow that automatically aggregates commit logs, open pull requests, and review activity into a summary posted to Slack each morning. For a ten-person team, this cuts standup time from 750 minutes per week to roughly 75 minutes of asynchronous reading—recovering ten hours monthly that developers spend writing code instead of talking about writing code.
Why GitHub Commit Summaries Work as Standup Replacements
GitHub commit logs already contain what developers did yesterday, what they’re doing today, and any blockers. The information exists in your version control system—you’re just not extracting it effectively.
Automated commit summaries capture:
- Completed work: Commits merged to main branch
- Work in progress: Open pull requests and recent branches
- Code review activity: Comments, approvals, and requested changes
- Blockers: PRs stuck in review, build failures, or dependency issues
This approach works especially well for teams with four to fifteen engineers where meeting overhead becomes noticeable but too much asynchronous communication creates notification fatigue.
Setting Up Your Commit Summary Script
Create a Python script that runs on a schedule and generates a summary report:
#!/usr/bin/env python3
"""Generate daily standup summary from GitHub activity."""
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
def get_git_log(days_ago=1):
"""Get commits from the last N days."""
since = (datetime.now() - timedelta(days=days_ago)).strftime('%Y-%m-%d')
result = subprocess.run(
['git', 'log', '--since', since, '--pretty=format:%h | %an | %s'],
capture_output=True, text=True
)
return result.stdout.strip().split('\n') if result.stdout else []
def get_open_prs():
"""Get open pull requests."""
result = subprocess.run(
['gh', 'pr', 'list', '--state', 'open', '--limit', '10', '--json', 'title,author,url'],
capture_output=True, text=True
)
return result.stdout
def generate_summary():
"""Generate the daily standup summary."""
commits = get_git_log()
summary = f"# Standup Summary - {datetime.now().strftime('%Y-%m-%d')}\n\n"
summary += "## Completed Work (Yesterday)\n\n"
for commit in commits:
if commit:
parts = commit.split(' | ')
summary += f"- `{parts[0]}` {parts[2]} ({parts[1]})\n"
summary += "\n## Work In Progress\n\n"
summary += "Run `gh pr list` to see open pull requests.\n"
summary += "\n## Today's Plan\n\n"
summary += "_Team members fill this in manually or via daily planning comment_\n"
return summary
if __name__ == '__main__':
print(generate_summary())
Save this as scripts/daily-standup.py and run it each morning with a cron job or GitHub Actions scheduled workflow.
Automating with GitHub Actions
Create a scheduled workflow that generates and shares the summary automatically:
name: Daily Standup Summary
on:
schedule:
- cron: '0 15 * * 1-5' # 3 PM UTC, Mon-Fri
workflow_dispatch:
jobs:
generate-summary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate commit summary
run: |
python scripts/daily-standup.py > standup-summary.md
- name: Post to Slack
uses: 8398a7/action-slack@v3
with:
status: custom
fields: repo,message
custom_payload: |
{
blocks: [
{
type: "header",
text: { type: "plain_text", text: "Daily Standup Summary" }
},
{
type: "section",
text: { type: "mrkdwn", text: "Generated from GitHub activity" }
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
This workflow runs automatically and posts results to your Slack channel. Team members check the summary asynchronously rather than attending a live meeting.
Adding Blocker Detection
Blockers often appear as PRs stuck in review, failing builds, or merge conflicts. Add detection logic:
def get_blockers():
"""Identify potential blockers."""
blockers = []
# PRs with no activity for 2+ days
result = subprocess.run(
['gh', 'pr', 'list', '--state', 'open', '--json', 'title,updatedAt,reviews'],
capture_output=True, text=True
)
import json
prs = json.loads(result.stdout)
for pr in prs:
updated = datetime.fromisoformat(pr['updatedAt'].replace('Z', '+00:00'))
hours_stale = (datetime.now() - updated.replace(tzinfo=None)).total_seconds() / 3600
if hours_stale > 48 and not pr.get('reviews'):
blockers.append(f"- {pr['title']} (no reviews, {hours_stale:.0f}h old)")
return blockers
def generate_summary_with_blockers():
"""Generate summary including blockers."""
summary = generate_summary()
blockers = get_blockers()
if blockers:
summary += "\n## 🚧 Blockers\n\n"
summary += "\n".join(blockers)
return summary
This highlights PRs that have been open for more than forty-eight hours without any review activity—clear indicators that something is blocked.
Making It Work for Your Team
Effective commit summary standups require consistent commit messages. Establish a team convention:
<type>: <short description>
- What changed
- Why it matters
- How to test (optional)
When developers write meaningful commit messages, the summary provides genuine value. Poor commit messages produce useless summaries regardless of your automation.
Some teams add a brief morning text update after reviewing the commit summary. Keep it to one sentence: “Working on X, no blockers” posted in Slack. This takes seconds and adds context the commit log cannot capture.
Calculating Time Savings
For a ten-person engineering team replacing fifteen-minute daily standups:
- Old way: 10 × 15 × 5 = 750 minutes weekly
- New way: 10 minutes daily reading + 5 minutes writing occasional updates = 75 minutes weekly
That’s ten hours recovered each month—time developers spend actually writing code rather than talking about writing code.
Related Reading
Built by theluckystrike — More at zovo.one