- Home
- Skills
- DevOps & Cloud
- Cross-Platform Code — Linux, macOS, Windows & Docker
Works with the AI Agents you already use
Cross-Platform Code — Linux, macOS, Windows & Docker
Makes code, tests and Docker builds behave the same on Linux, macOS and native Windows, and catches POSIX-only assumptions.
Free
Cross-Platform Code — Linux, macOS, Windows & Docker
Example session with this skill installed
Our CLI is getting Windows users and this export function fails for them. Make it cross-platform. Supported targets: Linux, macOS, Windows 11 (native, not WSL).
# reportr/export.py
import os, subprocess, datetime
def export_report(user_name, rows):
out_dir = os.path.expanduser("~/.config/reportr/exports")
os.makedirs(out_dir, exist_ok=True)
stamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
path = out_dir + "/" + user_name + "-" + stamp + ".csv"
with open(path, "w") as f:
f.write("\n".join(",".join(r) for r in rows))
subprocess.run(f"gzip {path}", shell=True, check=True)
os.system("rm -rf /tmp/reportr-cache")
return path + ".gz"
- Read your context and instructions
- Compiled the cross-platform code
This function can't work on native Windows as written. The filename contains : (from %H:%M), and it shells out to gzip and rm, which Windows doesn't have. Below are 8 findings, a portable rewrite and tests. The tests pass on Linux. macOS and Windows still need CI runs.
Platform check
- Platforms: Linux, macOS, Windows 11 native (as you specified).
POSIX assumptions: path separator, : in a filename, /tmp, ~/.config, locale-dependent encoding, line endings.
3. Undeclared external tools: gzip and rm are called through the shell.
4.
Failure on Windows: open() raises OSError on the : in the filename. If it got past that, gzip isn't on PATH.
5. OS-specific hot path: none. The standard library covers everything here, so no platform branch is needed.
Findings
| # | Line | Anti-pattern | Why it fails | Portable fix |
|---|---|---|---|---|
| 1 | stamp = ...%H:%M | : in filename | Forbidden on Windows; the space is also awkward in shells | UTC ISO-like stamp without : (%Y-%m-%dT%H%MZ) |
| 2 | os.path.expanduser("~/.config/...") | Hardcoded XDG path | Wrong location on Windows and macOS | platformdirs.user_data_dir("reportr") |
| 3 | out_dir + "/" + ... | String path concatenation | Mixes separators | pathlib.Path and the / operator |
| 4 | user_name in the filename | Unvalidated external input | CON, a:b, ../x break or escape the directory on Windows | is_safe_filename() check, rejected with a clear error |
| 5 | open(path, "w") | No encoding | Uses the Windows locale code page, so non-ASCII names get corrupted | encoding="utf-8" |
| 6 | "\n".join(...) in text mode | Line endings vary by OS | Text mode turns \n into \r\n on Windows only, so the bytes differ per OS | csv module with newline="", which writes identical bytes on every OS |
| 7 | subprocess.run(f"gzip {path}", shell=True) | Shell command plus shell interpolation | No gzip on Windows; a crafted user_name would be shell injection | stdlib gzip.open() in-process |
| 8 | os.system("rm -rf /tmp/reportr-cache") | POSIX utility and hardcoded /tmp | No rm or /tmp on Windows, and errors are silently ignored | shutil.rmtree(user_cache_dir("reportr")) |
Also, datetime.now() is local time. It is now UTC, as the "UTC at boundaries" rule requires. One finding is outside this skill's scope but worth fixing: ",".join(r) doesn't quote fields that contain commas. The csv module in the rewrite fixes that too.
Portable rewrite
# reportr/export.py
import csv
import gzip
import shutil
from datetime import datetime, timezone
from pathlib import Path
from platformdirs import user_cache_dir, user_data_dir # new dependency: add to pyproject
APP = "reportr"
WINDOWS_RESERVED = (
{"CON", "PRN", "AUX", "NUL"}
| {f"COM{i}" for i in range(1, 10)}
| {f"LPT{i}" for i in range(1, 10)}
)
FORBIDDEN_CHARS = set('<>:"/\\|?*') | {chr(c) for c in range(32)}
def is_safe_filename(name: str) -> bool:
if not name or name in {".", ".."}:
return False
if name.upper().split(".")[0] in WINDOWS_RESERVED:
return False
if any(c in FORBIDDEN_CHARS for c in name):
return False
if name.endswith((" ", ".")):
return False
return True
def export_report(user_name: str, rows: list[list[str]], out_dir: Path | None = None) -> Path:
if not is_safe_filename(user_name):
raise ValueError(f"user_name is not a safe filename on every OS: {user_name!r}")
out_dir = out_dir or Path(user_data_dir(APP)) / "exports"
out_dir.mkdir(parents=True, exist_ok=True)
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H%MZ") # no ':' (forbidden on Windows)
path = out_dir / f"{user_name}-{stamp}.csv.gz"
# gzip in-process instead of shelling out; newline="" so the csv module
# writes the same bytes (\r\n per RFC 4180) on every OS
with gzip.open(path, "wt", encoding="utf-8", newline="") as f:
csv.writer(f).writerows(rows)
shutil.rmtree(Path(user_cache_dir(APP)), ignore_errors=True)
return path
Behavior changes to note in the changelog: exports now go to the OS data directory (~/.local/share/reportr on Linux, ~/Library/Application Support/reportr on macOS, %LOCALAPPDATA%\reportr\reportr on Windows, all under exports/) instead of ~/.config. Timestamps are UTC. The return type is now Path.
Tests
# tests/test_export.py
import csv, gzip
import pytest
from reportr.export import export_report, is_safe_filename
@pytest.mark.parametrize("name", ["CON", "nul.txt", "a:b", "x/y", "..", "trail.", "trail ", ""])
def test_should_reject_name_when_unsafe_on_windows(name):
assert not is_safe_filename(name)
def test_should_write_portable_filename_and_roundtrip_utf8(tmp_path):
rows = [["name", "city"], ["Zoë", "São Paulo"], ["a,b", "x"]]
path = export_report("maria", rows, out_dir=tmp_path)
assert ":" not in path.name
with gzip.open(path, "rt", encoding="utf-8", newline="") as f:
assert list(csv.reader(f)) == rows
Ran on
Linux only (Python 3.14, pytest 9.1.1, platformdirs 4.12.4): 9 passed. These tests have
not run on macOS or Windows yet. Finding #1 is the Windows failure, and it is only proven fixed once the Windows lane is green.
CI matrix (required)
# .github/workflows/test.yml
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -e . pytest
- run: pytest -q
Validation checklist
- Path API only, no hardcoded separators
- Explicit UTF-8 on all file I/O
- No shell commands or POSIX utilities called from code
- No hardcoded
/tmpor~/.config - UTC at the boundary
- Filename validation for external input
- Tests green on macOS and Windows: pending CI
- README lists supported OS versions and the new export location
Next steps
- Add
platformdirsto your dependencies and merge the matrix workflow. Treat a red Windows lane as a blocker. - Search the rest of the CLI for the same patterns (
shell=True,os.system,expanduser("~/.,open(withoutencoding=) before declaring Windows supported. - Tell existing users that the export folder has moved, or keep reading the old path for one release.
Connects securely to your tools. The creator never sees your data.
About this skill
Ensuring Cross-Platform treats "it works on my machine" as unfinished work. Code is done when it behaves the same on every supported platform and tests on every one of those platforms prove it.
The problems it solves
- Paths built with
"/"string concatenation,open()with no encoding, andshell=Truecalls torm,gziporgrep - Hardcoded
/tmpand~/.config, and timestamps or user input that put:orCONinto Windows filenames - CI that only runs on Linux, so Windows bugs ship
- Docker images that build differently depending on the host OS
How it works: before any filesystem, shell, environment or subprocess code is written, the agent answers a five-question platform check. It then applies the required patterns: path APIs, explicit UTF-8, no shell, tolerant line-ending reads, OS-aware temp/cache/config dirs, filename validation, UTC at boundaries, and prebuilt native dependencies. When code is genuinely OS-specific, it goes behind one protocol with a runtime factory and fails loudly on unsupported platforms instead of falling back silently. Work is checked against a checklist covering code, tests, Docker and docs before delivery, and a CI matrix with fail-fast: false is required.
Default targets are Linux, macOS, native Windows (WSL doesn't count) and Docker amd64/arm64. You can narrow them per project.
What's inside
references/patterns.md: the required patterns in Python, TypeScript, Go and Javareferences/anti-patterns.md: a table of POSIX-only mistakes and their portable fixesreferences/platform-specific-code.md: the protocol + factory pattern, runtime detection, the branch mapreferences/docker.md: image hygiene, a.dockerignorebaseline, volume mounts, multi-arch builds
When it triggers: code that touches files, shell, subprocesses, encoding, time, paths, native dependencies or CI, or any mention of Windows, macOS, portability or Docker.
Free and MIT-licensed, part of the claude-skills-czl collection.
Source on GitHub: this skill · full repository
How to use
Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
Free forever
No account required to browse
Trust & safety
Security scanned
Verified clean 11 days ago
- Free to download with an account
Requires