The question looked innocent: when do I write this blog? A calendar cannot
answer that, because what goes into a calendar is intention. The real record
sits somewhere else — in the blog's own repository, written automatically at
the head of every commit as a timestamp. So I sat down and counted all of them.
I did not like the resulting picture. Then I realised it was wrong; then wrong
again. After two corrections what remained was smaller, and it told me
something about the blog I had not expected: most of this repository is not
written by me.
Every number below comes from a single snapshot taken at 08:40:41 on
2026-10-08. That has to be said up front, because this list grows while you
count it: five minutes after the first pass the total had already moved. The
easiest way to publish inconsistent numbers is to put two figures captured at
two different moments into the same sentence.
3,470 Commits, Six Months, One Question
Merging every branch of the blog's repository gives 3,470 commits between
31 March 2026 and 8 October 2026: 192 days. Collecting it takes this much:
git log --all --no-merges \
--pretty=format:'%H%x09%ai%x09%ci%x09%ae%x09%an%x09%cn%x09%s' > commits.tsv
One cleanup first: --all walks every branch, so a branch I rebased enters the
list a second time with new SHAs. Deduplicating on the (author date, subject)
pair produced 7 extras — two in a thousand. Rebase copies are not a problem
in this repository; I still mention it, because without measuring I could not
have called it unimportant. The remaining 3,463 commits are what I use from
here on.
My first curiosity was the hour-of-day distribution. I tallied the hour of the
raw %ai field and found 473 commits between midnight and six in the
morning: 13.7 per cent. One in every seven and a half commits on this blog
appeared to have been made in the dead of night.
Two Different Clocks in the Same Column
Something caught my eye: some of those night commits looked like work that
would not require me to be awake. So I looked at the third part of the
timestamp — the timezone offset:
2111 +0300
1352 +0000
There sits the mistake. Git's internal date format is defined plainly in its
own documentation: <unix-timestamp> <time-zone-offset> — seconds since the
epoch, plus an offset from UTC. The instant is stored absolutely; the
offset beside it describes the local time of the machine that wrote it.
git log follows the same logic when printing. The sentence in the docs reads:
"By default, dates are shown in the original time zone (either committer's or
author's)." So my histogram had two clocks side by side: 2,111 commits written
in Istanbul and 1,352 written in UTC. Adding them up in one column does not
produce a rhythm; it produces the overlapping shadow of two rhythms.
The blog's FAQ worker is the cleanest example. It has nineteen commits, all in
UTC:
2026-05-10 04:40:22 +0000
2026-05-10 14:05:41 +0000
2026-05-11 04:48:26 +0000
The same commits under --date=iso-local:
2026-05-10 07:40:22 +0300
2026-05-10 17:05:41 +0300
2026-05-11 07:48:26 +0300
Read raw, that job puts ten commits in the night band; converted to one clock,
zero. This is where I had to look harder at my own evidence, because my
first sentence was wrong. I was about to write "so a job that runs at seven in
the morning was filed at four a.m." Then I opened its cron: 30 0 * * *, which
is 00:30 UTC = 03:30 Istanbul. That job really is triggered in the middle
of the night.
So neither reading tells me when the job ran. The raw stamp (04:40) points at
the night for the wrong reason; the local stamp (07:40) is when the commit was
actually written, not when the job fired. The four hours in between were spent
in a queue — the GitHub page I cite says so itself: scheduled events can be
delayed during periods of high load.
A note here, because I had this wrong myself: -local has no effect on
--date=unix, but it does affect --date=raw. The seconds field of raw is
always UTC; the timezone field next to it changes with -local. I tested it on
my own machine: the same commit printed +0300, then +0000 under TZ=UTC,
then +0900 under TZ=Asia/Tokyo. "Raw is always UTC" is wrong; what is
always UTC is raw's seconds field.
RFC 3339 has a lovely line about this: the local offset is "often useful
information" — in email, for instance, it hints at how quickly someone might
reply. The offset is information about the person, not about the instant. The
same document distinguishes -00:00 from +00:00: the first means "I know the
UTC time but not the local offset", the second means "UTC is the preferred
reference here". What my machines write is the second — and that is exactly why
they are indistinguishable from a human sitting in Reykjavík at three in the
morning. I was going to write London, then checked the calendar: Britain is on
summer time for this entire window, so a person there would write +0100. The
same trap, even in choosing an example.
If it were up to me, every "work rhythm" chart would carry the clock it was
drawn against in its title. Most dashboards do not show it; they have a line
that quietly discards the offset instead.
433 of the Night's 463 Commits Are Not Mine
I converted everything to Istanbul time and counted again. The night total fell
from 473 to 463 — 13.4 per cent instead of 13.7. A difference of ten commits; I
was ready to call the whole correction a waste.
Good thing I did not. Looking one by one, 136 commits left the night band
and 126 entered it. That is 262 commits — 7.6 per cent of the corpus —
changing sides, with two opposing flows cancelling out so that the total barely
moved. Skipping the correction would not have given me a wrong number; it would
have given me a plausible-looking one, with 262 commits sitting in the wrong
bucket underneath. The most dangerous error in a measurement is the one that
cancels itself out in the total.
Because the real split was not in the clock but in the signature. I divided
the commits by author email:
| Who | Commits | Share |
|---|---|---|
| Machines (6 addresses, 9 name signatures) | 2,414 | 69.7% |
| Me (6 addresses, 4 name spellings) | 1,049 | 30.3% |
| Total | 3,463 |
In my own blog the machines out-commit me 2.3 to 1. I write the article; they
mostly make the commit. The night band sharpens the picture further:
| Night 00:00–05:59 | Commits | Share of own total |
|---|---|---|
| Machines | 433 | 17.9% |
| Me | 30 | 2.9% |
And here is the figure that matters: the 03:00 bucket holds 86 commits, and
not one of them is mine. The 02:00, 04:00, 05:00 and 06:00 buckets hold none
of mine either. My entire night consists of nine commits at 00:00 and
twenty-one at 01:00 — the tail of the evening. Within the night band, past one
in the morning I simply do not appear; my first commit of the day lands at
seven.
That ends the "man who works at night" image in a single line. The night shift
is real; I am just not the one working it.
I Built the Shift Myself, and Wrote Its Clock Myself
There is nothing to defend here, because those machines are mine. The cron
lines of the workflows still carry notes I wrote by hand months ago:
- cron: '30 0 * * *' # 00:30 UTC = 03:30 Istanbul
- cron: '0 1 * * *' # 01:00 UTC = 04:00 Istanbul daily
- cron: '40 2,5,8,11,14 * * *' # 02/05/08/11/14 UTC = 05/08/11/14/17 Istanbul
GitHub's documentation says the same thing: "By default, scheduled workflows
run in UTC." So I did the conversion correctly back then. The person who forgot
to apply it while reading the same data six months later is also me.
The nine machine signatures break down like this:
| Signature | Commits |
|---|---|
| Content bot | 1,185 |
| Social posting bot | 1,131 |
| Video prompt bot | 27 |
| FAQ bot | 19 |
| Keep-alive bot | 18 |
| Translation bot | 12 |
| Code agent | 10 |
| Series publisher | 7 |
| Dependency bot | 5 |
The small ones are the cleanest examples, because they contain no noise — and
they point opposite ways. Read raw, the FAQ bot's nineteen commits put ten into
the night; converted to one clock, zero — yet as we just saw, that job really
does fire at 03:30 local time. The keep-alive bot runs 0 3 * * 1 on Mondays,
which is 06:00 Istanbul: raw it shows seven night commits, converted it
shows zero — and this time zero is the right answer, because the job genuinely
does not run at night.
The same correction gives the right answer for one job and the wrong one for
the other. The shared lesson: a machine commit's stamp tells you neither the
schedule nor the moment it ran, only the moment it got to write. If you
want to know when a bot actually ran, the place to look is the workflow file
and the run history (gh run list --json createdAt,startedAt), not the commit.
The content bot left a more interesting trace: of its 1,185 commits, 658 are
+0000 and 527 are +0300. The same job, two different clocks — and the
monthly breakdown gives the order: all UTC in April, mostly local time through
May and June, all UTC again from July on. The local window opened on 8 May and
closed on 25 June. The data is not lying; it is narrating its own relocation. While chasing
the job launchd retried 21,881 times
I ran into something similar: the frightening number came from the wrong
column, while the thing that was genuinely broken arrived in silence.
There Are Two Dates, and They Answer Different Questions
The deduplication step turned up one more thing. Every commit carries two
dates: when the work was written (author) and when it was recorded
(committer). Git's documentation defines --committer-date-is-author-date as
using the original author date "instead of using the current time as the
committer date" — so the default behaviour is for a rebase to preserve the
author date and drag the committer date to now.
I counted how far the two drift apart in my own commits: in 179 of 1,049
(17.1 per cent) the committer time differs from the author time. Most differences
are measured in seconds; the gap exceeds an hour in only 6 commits, 5 land on a
different day, and the longest divergence is 22.3 hours. The direction is
one-sided: in every case the committer time comes later. The likeliest reading
is that I wrote the work one evening and rebased and pushed it the next day.
I got lucky here — in this repository the two columns sit close together. But
that is no guarantee. On long-lived branches that span days they diverge, and
then asking the committer date when you worked gets you the answer to when you
pushed.
Machines Do Not Know It Is Sunday
Looking at weekdays puts two rhythms side by side, and they look nothing alike:
| Day | Me | Machines |
|---|---|---|
| Monday | 189 | 362 |
| Tuesday | 199 | 364 |
| Wednesday | 133 | 336 |
| Thursday | 164 | 306 |
| Friday | 199 | 340 |
| Saturday | 134 | 361 |
| Sunday | 31 | 345 |
My column collapses on Sunday: 31 commits, not even a fifth of a normal day. In
the machines' column Sunday does not exist — 345, an ordinary day. Saturday
likewise.
I stared at that table for a while. The fact that the blog publishes on Sundays
does not mean I work on Sundays; the opposite, really — those jobs exist so that I
would not have to.
The daily distribution says the same. I committed on 138 of the 192 days,
the machines on 178. The averages look close (7.6 against 13.6), but the
medians give the difference away: my median day is 5 commits, theirs is 13. My
distribution spikes — my busiest day is 8 May with 65 commits; their busiest is
47, and even that is not unusual for them.
The hourly shape carries the same texture. Between the machines' quietest and
busiest hour there is only a 2.9× difference, and their curve never touches
zero. Mine is exactly zero for five straight hours (02:00–06:59); then ten at
seven, 115 at eight, 146 at nine. One of us works; the other flows. On a
chart the two look nothing alike.
I Am One Person; Git Thinks I Am Six
The second correction is the most embarrassing. Collecting my own commits
turned up six different author addresses:
| Address | Commits |
|---|---|
| personal (old) | 813 |
| corporate GitHub (noreply) | 106 |
| my first-ever address | 57 |
| work address | 57 |
| company address | 12 |
| my blog domain | 4 |
On top of that my name is recorded in four spellings: the all-lowercase form
854 times, the proper spelling 122, the surname-in-caps form 61, and a variant
with an organisation tag appended 12 times — 1,049 again. In contribution graphs each becomes
a separate, thin, faded line. Six weak lines do not show a person's rhythm.
Git has a ready answer: a .mailmap file at the top level of the repository
which, in the words of its documentation, maps "author and committer names and
email addresses to canonical real names and email addresses". Six lines were
enough to test it:
git -c mailmap.file=/tmp/mine.mailmap shortlog -sne --all --no-merges
My six-way split collapsed into a single line. The mapping performs no magic;
it does exactly what you tell it: on my first attempt I had forgotten to put
one address in the file, and that address stubbornly kept its own line.
Ten Minutes to Read Your Own Repository
If you want to ask yourself the same question, here is the order — each step
closes the error of the step before it:
- Take one snapshot. Write every commit into one file and derive every number from that file. Never put two figures captured at two different moments into the same table.
-
Drop the copies first.
--allcounts rebased branches twice. Deduplicate on(author date, subject); even if it changes nothing, you cannot call it unimportant without measuring. -
Convert to one clock. Use
--date=iso-local, or apply the offset with a timezone database. If you read raw%ai, you are summing different time zones in one column. Adding a fixed number breaks wherever daylight saving applies, andiso-localuses today'sTZ, not where you were that day. -
Separate the machines. Bot addresses, CI committer names, piles of
+0000. Your automation's night shift is not your night shift. -
Merge your own identities. Write a
.mailmap, then runshortlog -sneto see how many people you have been split into. Probably more than one.
I also counted what skipping step 3 does here, and the lesson is not in the
total: the share reads 13.7 per cent instead of 13.4 — almost nothing. Beneath
that "almost nothing" sit 262 commits that changed sides. Do not judge a
correction by how much it moves the total.
The Blog Has a Night Shift; I Am Not On It
I started this piece asking when I work and finished it knowing I am not the
only one working here. I asked a similar question ten days ago while counting
my laptop waking 76 times in seven days;
the answer there was "most of those wakes are not mine". This time it went a
step further: a machine's stamp does not even tell you when the machine ran.
The two corrections — mixed clocks, unlabelled signatures — are exactly the
mistakes I write about in production systems. When
I got five different answers for free disk space
I fell into the same class of error. I assumed I was more careful with the data
of my own life. No such privilege exists.
The remaining answer is calmer than I expected. What git was telling me with
"you work at night" was really this: you have built things that work at night.
Eighty-six commits land at three in the morning and I make none of them. The
blog publishes on Sunday while I draw the faintest line of my week at 31
commits.
That was the whole point of the automation. The strange part is that while
reading my own history I briefly mistook their work for mine.
And one more thing I noticed. Letting the bots commit under my own name would
have been technically easier, and it would have made my contribution graph look
fuller. Giving them separate signatures is the only reason this article was
possible at all: had the signatures mixed, I would have had no way to separate
2,414 commits from 1,049. Do not let your automation sign as you — six months
from now you will want to measure your own work.
The number that flattered the chart was not the true one; the number that shrank
it was. 1,049 commits, 138 days, nothing after two in the morning. That is a
full enough six months.
Official Sources
- Git — Date Formats (the internal
<unix-timestamp> <time-zone-offset>form) - Git —
--date=options: dates are shown in the original time zone by default - Git — pretty-formats: definitions of
%ai,%ad,%an,%ae,%cn - Git — git-rebase:
--committer-date-is-author-dateand preserving the author date - Git — gitmailmap: mapping author/committer names and addresses to canonical ones
- RFC 3339 — Date and Time on the Internet: local offsets and the
-00:00distinction - GitHub Docs — Scheduled workflows run in UTC by default
Top comments (0)