DEV Community

Orvi Das
Orvi Das

Posted on

Your Team Celebration Is an Event Handler. Make It a Cron Job (With the Workflow)

Compare your team's #shipped channel with your incident log. My guess is the quiet weeks in one line up with the bad weeks in the other.

That isn't a coincidence. It's a dependency. Most teams wire celebration to output: a release goes out, someone posts the party emoji, people get a nice moment. No release, no moment. In code terms, celebration is an event handler subscribed to on: release. When the events stop, the handler never runs.

I think that's a design bug. This post covers the research behind that, then the boring fix: run the ritual on a schedule, whatever the metrics say.

The research everyone cites, read backwards

"Celebrate small wins" usually goes back to Teresa Amabile and Steven Kramer's progress principle. They studied nearly 12,000 daily diary entries from 238 knowledge workers at seven companies. People's best workdays were the ones where they made progress on meaningful work. In their 2011 HBR piece they also asked 669 managers to rank what motivates employees. Progress came last. Only 35 of the 669 ranked it first.

The finding is solid. What people did with it is the problem. Amabile was making a point about visibility: managers don't see progress, so nobody marks it. The popular version turned that into "notice your wins, then reward yourself." That move welds the ritual to your throughput. Now celebration carries all the variance of your output.

Think about who gets nothing under that model:

  • the team in week three of a migration that won't demo until next quarter
  • the on-call engineer who spent five days keeping a fire small
  • the solo dev whose launch flopped on Tuesday

Those are the people with the least motivation left. They're the ones the ritual should be serving.

"Won't celebrating make us soft?"

This is the serious objection. The argument is that tension drives effort and celebrating lets it out. It sounds tough-minded. The data doesn't back it up.

In 2010, Michael Kraus, Cassey Huang and Dacher Keltner coded the physical touch between NBA teammates early in the 2008–09 season: fist bumps, high fives, chest bumps, huddles. Teams that touched more early in the season played better later in it, even after controlling for player salary, preseason expectations and early-season performance. The acknowledgment came first. It wasn't a reward for winning.

Separately, Alison Wood Brooks and colleagues found that doing a short, arbitrary ritual before a stressful task reduced anxiety and improved performance compared with doing nothing. The ritual didn't have to mean anything. It just had to happen reliably, before the outcome was known.

So the order is the other way around from what most teams assume: ritual, then performance. Not performance, then ritual.

Moving it from on: release to on: schedule

Roads get built before the traffic shows up, and they get maintained in weeks when nobody drives on them. A team ritual needs the same treatment. Here's a minimal version: a GitHub Action that posts a Friday marker to Slack every week. It doesn't matter whether anything shipped.

# .github/workflows/friday-marker.yml
name: friday-marker
on:
  schedule:
    - cron: "0 15 * * 5"   # Fridays, 15:00 UTC
  workflow_dispatch:
jobs:
  mark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: python3 scripts/friday_marker.py
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
"""Friday marker: posts every week, whether or not anything shipped."""
import json
import os
import subprocess
import urllib.request
from datetime import datetime, timedelta, timezone

SINCE = (datetime.now(timezone.utc) - timedelta(days=7)).strftime("%Y-%m-%d")
REPO = os.environ["GITHUB_REPOSITORY"]


def merged_prs() -> list[dict]:
    out = subprocess.run(
        ["gh", "pr", "list", "--repo", REPO, "--state", "merged",
         "--search", f"merged:>={SINCE}",
         "--json", "title,author,deletions", "--limit", "100"],
        capture_output=True, text=True, check=True,
    )
    return json.loads(out.stdout)


def build_message() -> str:
    try:
        prs = merged_prs()
    except Exception as exc:  # the ritual runs even when the data source doesn't
        print(f"gh failed, posting anyway: {exc}")
        prs = []

    lines = [f"*Friday marker, week of {SINCE}*"]
    if prs:
        deleted = sum(p["deletions"] for p in prs)
        lines.append(f"{len(prs)} PRs merged, {deleted:,} lines deleted.")
        lines += [f"• {p['title']} ({p['author']['login']})" for p in prs[:5]]
    else:
        lines.append("Nothing merged this week. The marker fires anyway.")
    lines.append("Thread: one thing you learned, fixed, unblocked, or survived.")
    return "\n".join(lines)


def post(text: str) -> None:
    req = urllib.request.Request(
        os.environ["SLACK_WEBHOOK_URL"],
        data=json.dumps({"text": text}).encode(),
        headers={"Content-Type": "application/json"},
    )
    urllib.request.urlopen(req, timeout=10)


if __name__ == "__main__":
    post(build_message())
Enter fullscreen mode Exit fullscreen mode

Two design choices matter more than the rest of the code.

The empty branch is a first-class path, not an error. If a script exits quietly when there's no data, you've rebuilt the event handler with extra steps. A zero-PR week is when this job matters most, so it posts the most deliberate message.

The data fetch is allowed to fail. The post isn't. If gh breaks, you still get a marker. Treat the ritual like a heartbeat: when it doesn't arrive, that should be a signal in itself.

One ironic catch: in public repos, GitHub disables scheduled workflows after 60 days with no repository activity. So the platform itself couples the schedule to output. If your project goes through long quiet stretches, put the marker in a repo that sees regular commits, or run it from somewhere else.

What goes in the thread on a dead week

The prompt at the end does the real work. On a good week people post features. On a flat week they need permission to count other things. These are the ones I've seen land:

  • Deletions. Removing 2,000 lines of dead code is progress nobody demos.
  • Incidents contained. "It was down for 40 minutes, not four hours" deserves a marker.
  • Unblocking. Name the person who answered the question that saved someone a day.
  • Learned the hard way. A postmortem finding counts. So does a hypothesis you killed.

Amabile's own data supports this. What showed up in people's best days was perceived progress on meaningful work, and you can only perceive what somebody points at. The Friday thread is where the pointing happens.

Where this came from for me

I nearly cancelled a standing Friday dinner a few weeks ago. An account I'd spent a quarter growing had just been suspended for the second time in nine weeks, and marking the week felt absurd. Then I noticed I was about to switch off the one recurring thing built for weeks like that one. The dinner happened. The account is still under review. The dinner was never meant to depend on how the account was doing.

The longer version, with more of the ritual research, is on my site. The short version for engineering teams: if your celebration has an if shipped: guard, take it out.

Does your team have a ritual that still runs on bad weeks, or does it go quiet when the metrics do?


Originally published at https://www.robatdasorvi.com/chapters/human/why-celebration-is-not-optional-it-is-infrastructure

Top comments (0)