DEV Community

Taylor Wang
Taylor Wang

Posted on

48-Hour Field Notes: The Patch Passed Locally Because TZ Lived in My Shell

Have you ever shipped a green local suite, then watched CI fail on a box that never loaded your shell profile? I spent forty-eight hours treating that mismatch like a flake, and the flake kept a boring secret the whole time. My laptop exported TZ from a forgotten line in ~/.zshrc, so every timestamp assertion looked honest on my machine. The assistant-generated patch never left that room, which is why the remote clock told a different story.

The symptom that wasted the first day

The failing test stamped report filenames with the local calendar day, then compared that string against an expected UTC date. Locally, pytest tests/test_report_name.py passed on every retry I threw at it during office hours. On CI, the same assertion died after 17:00 local time, which I misread as an unlucky runner instead of a clock boundary. I asked the usual questions out loud: was pytest caching an old module, was Docker's clock skewed, or was the wheel stale?

What I tried before I suspected the shell

None of those hunches survived a second look, but they still ate the first evening and most of the next morning.

  1. Deleted .pytest_cache and the local venv, then reinstalled from requirements.lock.
  2. Forced PYTHONHASHSEED=0 even though the failure was a date string, not a shuffled set.
  3. Printed datetime.datetime.now() inside the test, which matched my wall clock and made me overconfident.
  4. Blamed the coding assistant for nondeterministic codegen because the patch had arrived from a chat window.

That last accusation was unfair, and I should have seen it sooner than I did. The generated code was deterministic on every rerun I captured. My interactive environment was not deterministic, because login files kept coaching the process. Have you ever treated a green bar as evidence when the green bar only exists inside your own prompt?

A minimal reproduction you can run

I reduced the exporter to one function that should be timezone-explicit and currently is not. Save this as report_name.py if you want to follow the same trail, and treat it as a labeled example rather than production code.

from datetime import datetime

def report_filename(prefix: str = "daily") -> str:
    # Labeled example: this is the buggy shape I actually shipped.
    stamp = datetime.now().strftime("%Y-%m-%d")
    return f"{prefix}-{stamp}.csv"
Enter fullscreen mode Exit fullscreen mode

The test I trusted mixed an aware UTC expectation with a naive local stamp, which Python allows and still gets wrong.

from datetime import datetime, timezone
from report_name import report_filename

def test_report_filename_uses_utc_day():
    expected = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    assert report_filename() == f"daily-{expected}.csv"
Enter fullscreen mode Exit fullscreen mode

On my laptop the assertion passed while local time and UTC still shared a calendar day. After late afternoon, UTC had already rolled the date, and CI failed while I was making dinner. Have you checked whether your so-called UTC test actually constructs datetime.now(timezone.utc), or only agrees with your office hours?

The env dump that ended the guessing

I finally printed the process environment instead of arguing with the clock, using a one-liner I should have run on hour one.

python3 -c "import os,time,datetime as d; print('TZ=', os.environ.get('TZ')); print('tzname=', time.tzname); print('local=', d.datetime.now()); print('naive_utc=', d.datetime.utcnow()); print('aware_utc=', d.datetime.now(d.timezone.utc))"
Enter fullscreen mode Exit fullscreen mode

TZ was set to a Pacific zone I did not remember exporting. time.tzname confirmed it, and datetime.utcnow() was already a yellow flag because it returns a naive datetime. Mixing that naive value with an aware UTC expectation is legal Python, and it is still a lying comparison. As of current CPython docs, utcnow() is the wrong tool; datetime.now(timezone.utc) is the explicit one.

Hour 20: the clean box that did not have my dotfiles

I needed a second machine that would not source ~/.zshrc before Python started. A container on my laptop was not enough, because I still bind-mounted $PWD and leaked env through compose files. The useful move was a remote shell that started from a login environment I did not author, with a cwd that had never seen my aliases.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

I used MonkeyCode's free model access to draft an isolation script, then ran that script on the free server option so my dotfiles could not coach the process. I am not going to invent model names, quotas, hardware, or timings I cannot verify from here. The value was boring in the best way: a second cwd, a second env, and a model that could rewrite the script after I pasted the remote dump.

Isolation script I actually kept

#!/usr/bin/env bash
set -euo pipefail

echo "== identity =="
uname -a
id
pwd

echo "== clock =="
date -u
date

python3 - <<'PY'
import os, time, datetime as d
print("TZ=", os.environ.get("TZ"))
print("tzname=", time.tzname)
print("local=", d.datetime.now())
print("utc_aware=", d.datetime.now(d.timezone.utc))
PY

echo "== env keys that usually lie =="
env | awk -F= '
  $1 ~ /^(TZ|LANG|LC_|PYTHON|PYTEST|VIRTUAL_ENV|PATH)$/ {print}
' | sort
Enter fullscreen mode Exit fullscreen mode

On the remote box, TZ was unset, tzname was UTC, and the same pytest file failed immediately. Local green versus remote red left no flake left to argue with. Would I have seen that if I had kept asking the model to make the test more resilient? Probably not, because resilient often meant assert less.

A cheap preview still exists on the laptop if you do not want to open a remote shell yet.

env -i HOME="$HOME" PATH="/usr/bin:/bin" TZ=UTC python3 -m pytest tests/test_report_name.py -q
Enter fullscreen mode Exit fullscreen mode

If that command disagrees with your normal interactive shell, you do not have a flake. You have a profile, and the profile is winning.

What the assistant kept doing wrong

I asked for a fix three times before I pasted the remote env dump into the transcript. Each reply looked brilliant on my laptop and collapsed on the clean box, which should have been the review checklist by itself.

  • Pin the expected date to datetime.now().date() in the test, which hides the production bug.
  • Sleep and retry around midnight, which is how flakes get a lease on life.
  • Hard-code America/Los_Angeles in the exporter, which just relocates the landmine to the next hire.

None of those patches survived the clean shell, and that is the part I would repeat as a habit. The model was not evil; it optimized for the transcript I gave it, and that transcript was a local success. If your prompt never includes env | sort from a machine you do not own, the model will keep sanding down the test until the red bar goes quiet.

The patch I would actually keep

from datetime import datetime, timezone

def report_filename(prefix: str = "daily", now=None) -> str:
    clock = now or datetime.now(timezone.utc)
    if clock.tzinfo is None:
        raise ValueError("report_filename requires an aware datetime")
    stamp = clock.astimezone(timezone.utc).strftime("%Y-%m-%d")
    return f"{prefix}-{stamp}.csv"
Enter fullscreen mode Exit fullscreen mode

The test injects noon UTC and 23:30 UTC so office hours cannot rescue you, and it never calls datetime.now() for the expected value.

from datetime import datetime, timezone
from report_name import report_filename

def test_filename_is_utc_even_late_in_local_day():
    noon = datetime(2026, 9, 16, 12, 0, tzinfo=timezone.utc)
    late = datetime(2026, 9, 16, 23, 30, tzinfo=timezone.utc)
    assert report_filename(now=noon) == "daily-2026-09-16.csv"
    assert report_filename(now=late) == "daily-2026-09-16.csv"

def test_naive_datetime_is_rejected():
    naive = datetime(2026, 9, 16, 12, 0)
    try:
        report_filename(now=naive)
    except ValueError:
        return
    raise AssertionError("naive datetime should not be accepted")
Enter fullscreen mode Exit fullscreen mode

Notice the assertions no longer consult the wall clock at all. That is the whole lesson, dressed up as two tests and one guard. If a future assistant reintroduces datetime.now() in the expected value, the late-day case should fail on any machine, not only on CI after dinner.

Decision table I wish I had on hour one

Signal Trust local laptop? Need a clean remote shell? Ask the model again?
Failure mentions dates, logs, or filenames No Yes Only after you paste date -u and TZ
Test uses datetime.now() or utcnow() No Yes Ask it to inject a clock, not to retry
Passes only inside your interactive shell No Yes Do not paste the local green output alone
Fails identically under env -i Maybe the code Optional Yes, now the transcript is honest
Depends on GPU, licensed data, or secrets Stop Not this workflow Not this workflow

I would tape that table above the chat window next time, because the chat window is extremely good at agreeing with whatever machine you are sitting at. The table is not clever. It is a speed bump that asks whether the evidence was collected somewhere your dotfiles cannot reach.

What broke when I tried to be clever

The remote session still surprised me, which is why these notes are not a victory lap. python pointed at a distro interpreter that lacked pytest, so I almost declared the server broken instead of creating a venv. date without -u printed a locale string I misread as UTC because the offset was +0000. I also uploaded the test file and forgot report_name.py, which produced ModuleNotFoundError and a fresh twenty minutes of noise that had nothing to do with timezones.

Sequence I would repeat

I would still repeat the boring sequence, because every shortcut I took sent me back to the laptop's green bar.

  1. Dump identity, clock, and a small allow-list of env keys on both machines before opening a chat.
  2. Run the same pytest invocation under env -i locally, with TZ=UTC, before asking for a patch.
  3. Paste the two dumps into the model, not the stack trace alone, so the transcript contains the lie.
  4. Require an injected clock in any patch that touches timestamps, filenames, or log lines.
  5. Re-run the patch on the clean box before I trust the local green bar, even if the diff looks tiny.

If you want a second machine that does not carry your .zshrc, MonkeyCode's free server option is a straightforward place to park that isolation script. That is the only product nudge I need here, and the workflow still stands if you use any other clean shell you already own.

Limitations, and who should skip this

This workflow will not help you if the bug lives inside a vendor API, a hardware device, or a dataset you cannot copy onto a second box. A free remote shell also will not reproduce GPU kernels, macOS-only filesystem quirks, or corporate SSO cookies, and you should not upload secrets just to win an argument with CI. I also would not use an assistant to stabilize tests by weakening assertions, because that is how timezone bugs become quarterly incidents instead of one loud failure.

The field notes are a method, not a guarantee, and they assume you can run the same files in two environments. Clocks, locales, and shell profiles still lie, and they lie faster if you only measure them at your desk. Would I start with the clean dump next time, before I accuse pytest of flaking on a date string? Yes, and that is the only habit from these forty-eight hours I plan to keep.

Top comments (0)