DEV Community

Taylor Wang
Taylor Wang

Posted on

I Diffed Locale and umask Before I Let the Health Check Land

I started these notes because a small health check looked finished on my laptop and then fell apart once a second host entered the picture. The draft opened text files without an encoding, printed the umask by calling the setter, and assumed my home directory would accept a cache write. Would you merge that patch if the only green run still lived on the machine that authored the file? I would not, so I spent two days turning those quiet assumptions into a manifest I can diff.

Hours 0 to 8: the draft looked done

The job was boring on purpose, which is exactly why I almost trusted the tidy output too early. I wanted a short Python check that prints the interpreter, the working directory, the locale, and whether a cache directory is writable. A generated draft arrived in one pass, and the local run printed a neat block that looked like a finished tool. Have you ever accepted a neat printout simply because the function names sounded like production code?

I had used MonkeyCode's free model access to sketch that first draft, and a free server option was available for the second run. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I am writing inside that outreach, not as an independent benchmark lab with a score to defend. I am not claiming a quota, a machine shape, or a promise that free access stays fixed. The habit below still works if you swap both tools for a laptop container and a text editor.

The draft carried three lines that I now refuse to skim past when I review a generated check. It called os.umask(0) to read the mask, it used open(path) with no encoding argument, and it wrote under Path.home() without a fallback path. On my laptop those lines looked harmless, because my locale is UTF-8 and my home directory is mine. Why would a second host preserve any of those comforts just because the sketch assumed them?

Hours 8 to 24: the second host disagreed

I copied the same file into the free server job and ran the same command from a clean working directory. The process did not share my laptop locale, and the cache probe did not share my laptop home directory either. I am not publishing timings or a pass rate, because this was a lab comparison rather than a study. What I can repeat is the shape of the disagreement, and you can reproduce that shape with the script below.

Three failures showed up in the lab before I touched the patch I actually cared about. The umask helper left the process mask at zero, so a later log file was created world-writable on that host. A notes file with a non-ASCII comment failed to decode when the preferred encoding was not UTF-8. The home cache probe returned a permission error, and the draft had no branch for that result at all.

Would a green local pytest have caught any of those failures, if every test stayed on the laptop? I do not think so, because the tests inherited the same locale, the same home, and the same starting directory. I also noticed a quieter lie hiding underneath the successful exit code returned by that server job. The draft assumed the process had started inside the repository root on every host I would use.

The server job started in a scratch directory that I had never named anywhere in the prompt. Relative paths then opened the wrong file, or they opened nothing while the check still exited zero. That is the kind of green result I no longer treat as a reason to merge.

The manifest I would run again

The artifact is a single script that prints canonical JSON and never touches the network at all. Run it on the laptop, run it on the second host, and diff the two files before you read any explanation. I left a broken helper in the file so the umask trap stays visible, and that helper is not called by main. Treat the whole file as a lab recipe, not as a log pulled from a customer incident.

#!/usr/bin/env python3
"""Host manifest for a second-host diff.

Lab recipe, not a measured outage. The cache probe may create
~/.cache/manifest-lab and leave that empty directory behind.
"""

from __future__ import annotations

import json
import locale
import os
import sys
import tempfile
from pathlib import Path

ALLOW = ("LANG", "LC_ALL", "LC_CTYPE", "PYTHONIOENCODING", "PYTHONUTF8", "TZ")


def read_umask() -> str:
    """Return the current umask and restore it before we leave."""
    current = os.umask(0o022)
    os.umask(current)
    return oct(current)


def broken_read_umask() -> str:
    """Example only: this leaves the process umask at 0. Do not ship it."""
    return oct(os.umask(0))


def safe_locale() -> list[str | None]:
    try:
        return list(locale.getlocale())
    except ValueError as exc:
        return ["unavailable", exc.__class__.__name__]


def probe_dir(path: Path) -> str:
    try:
        path.mkdir(parents=True, exist_ok=True)
        probe = path / ".manifest_probe"
        probe.write_text("ok", encoding="utf-8")
        probe.unlink()
        return "writable"
    except OSError as exc:
        return f"blocked:{exc.__class__.__name__}"


def main() -> int:
    home = Path.home()
    manifest = {
        "cwd": os.getcwd(),
        "executable": sys.executable,
        "filesystem_encoding": sys.getfilesystemencoding(),
        "home": str(home),
        "home_cache": probe_dir(home / ".cache" / "manifest-lab"),
        "locale": safe_locale(),
        "preferred_encoding": locale.getpreferredencoding(False),
        "temp": probe_dir(Path(tempfile.gettempdir())),
        "umask": read_umask(),
        "version": sys.version.split()[0],
        "env_allowlist": {key: os.environ.get(key) for key in ALLOW},
    }
    json.dump(manifest, sys.stdout, indent=2, sort_keys=True)
    sys.stdout.write("\n")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode
python3 host_manifest.py > local.json
# Second host, same file, same command, clean working directory:
python3 host_manifest.py > remote.json
diff -u local.json remote.json
# Optional pressure test, then compare again:
PYTHONUTF8=0 LC_ALL=C python3 host_manifest.py > c_locale.json
Enter fullscreen mode Exit fullscreen mode
def read_notes(path: str) -> str:
    # Prefer an explicit encoding over open(path) with no encoding argument.
    with open(path, encoding="utf-8") as handle:
        return handle.read()
Enter fullscreen mode Exit fullscreen mode

How I read a diff without arguing with it

I sort the JSON so a noisy key order cannot pretend to be a real change. I ignore nothing in the allowlist, and I never print the rest of the environment beside those six names. If preferred_encoding differs, I stop, even when the health check text looks identical on both sides. A matching exit code is not a matching host, and I had to write that sentence down before I trusted myself.

A decision table I kept beside the notes

I did not want a vibe check at hour thirty, so I wrote the responses down before the diff tempted me to explain them away.

Diff field What I do before merging Why I stop
preferred_encoding Set PYTHONUTF8=1 on both sides, then rerun open() without an encoding will split
locale Treat C or an empty pair as a failed boarding Sort order and decoding both move
umask Reject a helper that sets the mask and never restores it Later files become world-writable
home_cache Refuse cache writes when the probe says blocked The draft will crash or write elsewhere
cwd Require an explicit repository root argument Relative paths lie from a scratch start
version Read the minor notes before accepting a pin Warnings and defaults move between minors
env_allowlist Diff only the named keys, never a full dump A full dump leaks tokens into the notes

Hours 24 to 40: I made the disagreement boring

The second day was mostly subtraction, which is less glamorous than another generated rewrite and much safer. I deleted the umask setter from the draft, passed encoding="utf-8" at every text boundary, and replaced the home write with an explicit --cache-dir. I also stopped asking anyone for a server-side fix before the JSON diff existed on disk. Is a long and fluent explanation actually useful when the two manifests still disagree on encoding?

When I did ask for a revision, I pasted the diff and the matching decision row, not the whole environment block. That kept secrets out of the prompt, and it kept the draft from inventing a locale I had not measured. Free model access was handy for that rewrite, but the manifest stayed the source of truth on my side. If that access is busy, limited, or simply gone later, the script still runs on whatever host you already have.

The free server option mattered for one narrow reason, and I want that reason kept small. I wanted a second host I could start without opening a billing argument, so the comparison would actually happen on a tired afternoon. I do not know the hardware, the neighbor load, or how long that option remains free. I will not fill those blanks with guesses, even when a tidy paragraph would look more confident.

A paid virtual machine, a local container, or a colleague's shell will teach the same lesson if the manifest stays honest. The point is the second host, not the logo printed beside the connect button on the page. If you already own a spare shell, use that shell and skip the product detour entirely.

Hours 40 to 48: what I would repeat

I would repeat the comparison itself, and I would drop the part where a tidy printout felt like proof. The list below is the part I want future me to follow when the next small tool shows up. None of these steps require a named model, a quota, or a story about how fast the draft arrived.

  1. Sketch with a model if you want a faster start, then mark every environment call untrusted until both manifests agree.
  2. Run the script on both hosts, and store both JSON files next to the patch that is under review.
  3. Force UTF-8 with PYTHONUTF8=1 when you own the job, and still pass an explicit encoding inside the application code.
  4. Read the umask with a restore afterward, or skip that read entirely when the program never creates files.
  5. Drop any check that still exits zero when the cache probe returns a blocked result to the caller.
  6. Drop any prompt that includes a full environment dump, a live token, or a private path under home.

Would I repeat the hour where I trusted the local printout just because the headings looked complete? I would not, because that hour did not even require a clever bug to waste the rest of the review. The bug was ordinary, which is exactly why I expect the same shape to show up on the next small tool.

Limitations, and who should skip this

This manifest does not prove the patch is correct, and a matching pair of JSON files only says the listed assumptions traveled together. A signal between the two os.umask calls can still leave the mask unrestored, which is rare and still worth naming in the notes. Calling locale.getlocale is not the same fact as asking for the preferred encoding, so the script keeps both fields. On Windows, umask is not the permission story most of us carry over from Unix hosts, so this recipe is the wrong tool there.

Skip this approach if you need an attestation for an audit, because a lab diff is not a signed record of any kind. Skip it if the job handles secrets, unless those secrets are already gone from the prompt and from the JSON output. Skip it if you need guaranteed capacity, a named accelerator, or a free tier you can plan around. I was not given those facts, and I will not invent them to make the notes sound more certain.

Skip it if your team already ships a locked image and a hermetic test runner for every change. You may simply be re-checking a problem that the image build has already closed for you. In that case the manifest is optional reading, not a new gate you need to add this week.

Where I left the notes

The patch I would actually land is the boring one, and I am fine with that result. It has an explicit encoding, an umask helper that restores the old mask, and a cache directory you pass in. It also carries a manifest diff inside the review note, so the next reader can see the host gap. If a free server seat is already sitting in your account, run the manifest there once before you merge.

Keep the JSON beside the patch, instead of trusting a paragraph that explains the disagreement away. The model can draft the next check whenever you want a quick sketch to react to. It does not get to decide that my laptop and the second host are the same machine.

Top comments (0)