Scheduling restic check and Borg check without locking out production backups

Integrity checks fail in two ways. The obvious one is that they find corruption. The less obvious one is that they never run, or run so rarely that a repository can rot for a year before anyone notices. For a small fleet — one to ten machines, one operator, a VPS or two and a homelab box — the second failure mode is the common one, and it is almost always a scheduling problem dressed up as a discipline problem.

The instinct is to bolt restic check or borg check onto the end of the nightly backup script. That instinct is wrong, but not for the reason usually given. The contention that matters is not between a read-only check and a backup. It is between the maintenance jobs that take exclusive locks — prune, forget, compact — and the backup that is trying to write while they hold the repository. A check scheduled carelessly next to a prune is how you get a backup that fails at 02:15 and a check that reports clean at 02:20, and nobody looks at either until a restore is needed.

The lock is the schedule

Restic’s own documentation is blunt about this. In the section on removing snapshots: “During a prune operation, the repository is locked and backups cannot be completed. Please plan your pruning so that there’s time to complete it and it doesn’t interfere with regular backup runs.” The same page adds: “It is advisable to run restic check after pruning, to make sure you are alerted, should the internal data structures of the repository be damaged.”

That is the whole design constraint in two sentences. Prune is the disruptive job. Check is the job that should follow it, because prune is the operation most likely to expose a structural problem. If you schedule them as one maintenance window, you get the check for free at the moment it is most informative. If you schedule them as two independent cron entries an hour apart, you have created a race between a job that holds an exclusive lock and a job that needs to read the repository, and you have done so for no benefit.

Borg’s situation is different in detail and similar in shape. borg check is read-only by default: “The check command is a read-only task by default. If any corruption is found, Borg will report the issue and proceed with checking. To actually repair the issues found, pass --repair.” The retrieved Borg documentation does not state whether check takes a repository lock or blocks a concurrent create, so do not assume either way. What it does state is that check “always needs the key, --repository-only included: it loads the key (asking for the passphrase, if needed) before it checks anything and aborts if the key can not be loaded.” That matters for scheduling: a check that cannot read the key fails before it touches the repository, and a cron job that swallows that failure looks identical to a clean run.

Restic: separate the jobs, then spread the reads

The practical restic pattern for a small fleet is three jobs, not one:

  1. Backup, nightly, on the production host, with a credential that can write but not delete.
  2. Forget and prune, weekly, on a maintenance host that holds the delete-capable credential.
  3. Check, immediately after prune, on the same maintenance host, in the same window.

Keeping the delete-capable credential off the production host is the append-only pattern, and it is worth the extra host because it changes what a compromised production server can do to your history. It also means the maintenance host is the natural place to run the check: it already has the password, it already has the repository access, and it is not competing with a backup for I/O on the machine that is actually serving users.

The check itself does not have to read every pack every week. Restic’s check accepts --read-data-subset, which resticprofile’s reference documents as: “read a subset of data packs, specified as ‘n/t’ for specific part, or either ‘x%’ or ‘x.y%’ or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset.” The n/t form is the one to use for a schedule, because it is deterministic: day 1 of 31, day 2 of 31, and so on. Over a 31-day month every pack is read once. Over a 30-day month, one group is skipped until the next long month — a real gap, but a bounded one, and far better than a full read-data check that never fits in the window and therefore never runs.

A percentage subset is simpler to write and worse to reason about. Restic’s own documentation notes that random subsets do not guarantee full coverage. If you use a percentage, you are choosing probabilistic coverage and you should say so in the runbook rather than pretending the schedule is exhaustive.

One more restic detail worth knowing before you panic at the output: restic check routinely reports pack files that are not referenced in any index. The FAQ is explicit that this is “not a source for concern” and is “with high probability this is duplicate data caused by an interrupted backup run or upload operation,” cleanable with restic prune. A check that exits clean but prints those lines is a pass, not an incident. Teach your alerting the difference, or you will page yourself at 3 a.m. for an interrupted upload from three weeks ago.

Borg: time-box the repository check, keep verify-data rare

Borg’s check splits into two phases that behave very differently under a schedule. The repository phase checks segment headers and the metadata and data of all objects, with size and CRC checks that catch bit rot. The archive phase checks archive metadata and, optionally with --verify-data, the actual file content.

The repository phase can be time-boxed. From the Borg 1.4 documentation: “The --max-duration option can be used to split a long-running repository check into multiple partial checks. After the given number of seconds the check is interrupted. The next partial check will continue where the previous one stopped, until the full repository has been checked.” The same page gives the arithmetic: a complete check that would take seven hours, run daily with --max-duration=3600, produces one full repository check per week.

There is a catch, and it is the one that bites people who skim. Partial repository checks cannot run archive checks. The 1.4 documentation states that with --max-duration “you must also pass --repository-only, and must not pass --archives-only, nor --repair.” The 2.x documentation repeats the constraint and adds the reason: partial checks “run neither archive checks nor repair mode.” So a time-boxed daily check verifies the repository’s bytes but says nothing about whether the archives’ metadata and chunk references are consistent. You need a separate, less frequent archive check to cover that half.

Borg 2.x adds --max-age, which lets a check reuse recent pack results: “packs whose intact result is younger than the given timespan (e.g. --max-age=4w or --max-age=12m) are skipped, spreading the verification cost over repeated checks.” This is not available in 1.x, so check which major version you are running before copying a schedule from a 2.x page. The 2.x documentation also notes that --max-age “affects only the repository check and cannot be combined with --archives-only or --repair.”

Then there is --verify-data. The 1.4 documentation calls it “a complete cryptographic verification and hence very time consuming, but will detect any accidental and malicious corruption.” The 2.x documentation is more emphatic: it “always verifies that each chunk’s content matches its chunk id, which normal reads do not do by default” and “running it periodically is therefore recommended.” Periodic does not mean nightly. For a small fleet, a monthly or quarterly --verify-data run, scheduled in a window long enough to finish, is the honest cadence. Putting it on a daily timer and hoping it completes is how you get a check that is killed halfway every night and never reports anything useful.

A schedule that fits one to ten machines

The following is a recommendation, not a finding. It assumes a nightly backup window, a maintenance host with delete rights, and a repository small enough that a full read-data pass is measured in hours rather than days. Adjust the cadences to your window; do not adjust the separation.

  • Nightly, production host: backup only. No check, no prune, no forget. The production host should not be able to delete its own history, and it should not be doing maintenance work while it is also serving.
  • Weekly, maintenance host: forget --prune, then restic check (structural, no --read-data) in the same window. This is the check the restic docs recommend after pruning.
  • Daily, maintenance host, staggered hours from the backup: restic check --read-data-subset=n/31, where n is the day of the month. Or, for Borg, borg check --repository-only --max-duration=3600 (1.x) or with --max-age=1w added (2.x).
  • Monthly, maintenance host: a full archive check. For Borg, borg check --archives-only without --max-duration. For restic, a full --read-data run if the repository is small enough, or accept the subset rotation as your coverage.
  • Quarterly, maintenance host: borg check --verify-data if you run Borg, in a window you have actually measured.

Stagger by hours, not minutes. If prune and check both need the repository and the prune takes longer than expected — and it will, the first time, because the first prune after a policy change repacks more than you think — a five-minute gap is a coin flip. A multi-hour gap is a schedule. Put the check after the prune in the same window, and put the daily partial check in a different window entirely, ideally at a time when no backup is running on any host that shares the repository.

Alert on “did not run” separately from “found errors”

This is the part that separates a schedule from a comfort ritual. A check that exits 0 because it found nothing and a check that exits 0 because it was skipped by a lock, or because the key was not readable, or because the wrapper swallowed a non-zero exit, look identical to a naive monitoring setup. They are not identical. One means the repository is intact. The other means you do not know.

Borg’s return codes are documented and worth wiring into your wrapper: 0 is success, 1 is a generic warning where “operation reached its normal end, but there were warnings — you should check the log,” 2 is a generic error, and with BORG_EXIT_CODES=modern the codes become more specific. A wrapper that treats anything other than 0 as a failure and anything other than 0 or 1 as a page is a reasonable starting point. Restic’s exit behavior is less granular, so the wrapper should also check that the check actually ran: a start timestamp, an end timestamp, and a duration that is not suspiciously short.

The failure mode to design against is the green check that never executed. If your monitoring only alerts on non-zero exits, a cron entry that silently stopped firing three months ago is invisible. Alert on the absence of a successful run, not just on the presence of a failed one.

What a clean check does not prove

A clean restic check or borg check proves the repository is internally consistent. It does not prove the repository contains what you need. A backup job that has been silently excluding the wrong directory for six months will produce a perfectly consistent repository with nothing useful in it, and every check will pass.

Pair the check schedule with a periodic restore test. Restore a file, restore a directory, restore a database dump, and time it. The check tells you the bytes are intact; the restore tells you the bytes are the right bytes. Neither substitutes for the other, and a schedule that only does the first is a schedule that will pass every test until the day it matters.

Before you trust the schedule

Three things to verify before you walk away from the cron file:

  1. Dry-run the policy. restic forget --dry-run and borg prune --dry-run print what would be removed without removing it. Run them once by hand, read the output, and confirm the retention rules match your intent. A policy that keeps nothing is a policy that will delete your history on the first real run.
  2. Confirm which host holds delete rights. If the production host can delete, the append-only split is not in place, and the check schedule is protecting a repository that a compromised server can still destroy.
  3. Restore something. Not a dry run, not a listing. An actual restore to a scratch directory, from the most recent snapshot, with the clock running. If it takes longer than your recovery objective allows, the check schedule is not the problem.

FAQ

Does restic check take an exclusive lock and block backups?

The retrieved restic documentation states that prune locks the repository and that backups cannot be completed during a prune. It does not state check‘s locking behavior. Do not assume either way; test it against your restic version, or simply schedule the check outside the backup window so the question does not matter.

Does borg check block a concurrent borg create?

The retrieved Borg check documentation does not state whether check takes a repository lock or blocks a concurrent create. It does state that check is read-only by default and that it always needs the key. Treat the lock question as open and schedule accordingly.

Can I use --max-duration with --archives-only?

No. Both the 1.4 and 2.x documentation state that --max-duration requires --repository-only and cannot be combined with --archives-only or --repair. A time-boxed check covers the repository phase only; archive checks need a separate, untimed run.

Is --max-age available in Borg 1.x?

It appears on the 2.x check documentation page and not on the 1.4 page. Check your version before using it in a schedule.

Why does restic check report packs not referenced in any index?

Restic’s FAQ describes this as uncritical, most likely duplicate data from an interrupted backup or upload, and cleanable with restic prune. It is not by itself a sign of corruption.

How often should I run --verify-data?

Borg’s 2.x documentation recommends running it periodically because normal reads do not verify chunk content against chunk IDs. The right cadence depends on your repository size and your window; for a small fleet, monthly or quarterly is a defensible starting point, provided the run actually completes. A daily --verify-data that gets killed halfway every night is worse than a quarterly run that finishes.

Monitoring Certificate Chains From the Inside: Three Probes, Three Failure Modes

A certificate check that returns a single boolean is not a monitor. It is a coin flip with extra steps. Expiry, trust-path validation, and revocation are three different questions, answered from three different data sources, and they fail in three different ways. Collapse them into one green light and the light will be wrong in at least one direction — usually the direction that gets you paged.

This is about probing your own endpoints from inside your network: the mail server, the reverse proxy, the internal API. It is not about watching Certificate Transparency logs for your domains. Those answer a different question — what has been issued in your name — and they fail differently. Keep them separate.

Why one check hides the interesting failures

RFC 5280 profiles the X.509 v3 certificate and the X.509 v2 CRL, and it describes an algorithm for certification path validation. That algorithm is the thing your TLS client actually runs. It checks signatures up the chain, validity periods at every depth, name constraints, key usage, and — if configured — revocation status. A monitor that only reads the leaf’s notAfter is checking one input to that algorithm and calling it a day.

The failure modes you care about are not the same failure:

  • Expiry is a property of each certificate in the chain, not just the leaf. An intermediate that expires before the leaf takes the whole endpoint down.
  • Trust-path failures are structural: a missing intermediate, a cross-signed alternate chain, a root that your trust store does not carry, a name mismatch. These are usually binary, but the reason is what you need.
  • Revocation is a network question with a third answer. The responder can say good, revoked, or unknown — and it can also fail to answer at all, which is not the same as good.

Each of these deserves its own probe, its own state model, and its own alert path.

Probe 1 — chain and trust

Verify the served chain against your own trust store, not the system default, and read the structured output rather than the exit status. The OpenSSL verify command verifies certificate chains and, per its manual, attempts to display all problems if a chain has multiple. That last property matters: a chain can have more than one defect, and fixing the first one you see may just reveal the second.

The output format is documented. The general shape is:

server.pem: /C=AU/ST=Queensland/O=CryptSoft Pty Ltd/CN=Test CA (1024 bit)
error 24 at 1 depth lookup: invalid CA certificate

The manual is explicit about depth: it starts at zero for the target (leaf) certificate, then 1 for the CA that signed it, and so on. That number is the difference between a five-minute fix and an hour of guessing. error 24 at 1 depth tells you the intermediate is the problem. error 10 at 0 depth tells you the leaf has expired. error 20 at 0 depth tells you the issuer cannot be found — usually a missing intermediate in what the server sends.

A probe that only checks $? throws that away. Parse the error number and the depth. Store both. Alert on the pair, not on the exit code.

Two practical notes on running this:

  • Fetch the chain the way a client would — openssl s_client -showcerts against the endpoint — and feed the leaf plus the served intermediates to verify with -untrusted, and your own roots with -CAfile or -CApath. If you verify against a different trust store than your clients use, you are testing a different system.
  • If you want the probe to also exercise revocation, verify accepts -CRLfile (a file or URI containing one or more CRLs) and -crl_download, which attempts to download CRL information via the certificates’ CDP entries. Both are useful, but see the revocation section below before you make them the primary signal.

Probe 2 — expiry, at every depth

The leaf is the certificate everyone watches. The intermediate is the one that surprises people, because it was issued years ago, renewed on someone else’s schedule, and is not in your renewal runbook.

The probe should extract notBefore and notAfter for every certificate in the served chain, compute the earliest expiry, and report which certificate at which depth owns it. A chain where the leaf has 200 days left and the intermediate has 12 is a chain with 12 days left.

This is also where cross-signed and alternate chains complicate things. A server may present one chain; a client may build a different path using a cross-signed root or an alternate intermediate it already has cached. Your probe sees what the server sends. That is the right thing to monitor for your endpoints, because it is what most clients will receive — but be aware that a successful verification on your probe does not prove every client will build the same path. If you care about a specific client population, probe with that population’s trust store.

Probe 3 — revocation, and the state everyone forgets

OCSP is specified in RFC 6960 as a protocol for determining the current status of a certificate without requiring CRLs. It defines three definitive status values: good, revoked, and unknown. Read the definitions before you write alert rules, because two of them do not mean what the words suggest.

good is narrower than it sounds. The RFC states that a good response does not necessarily mean the certificate was ever issued, or that the time the response was produced falls within the certificate’s validity interval. It means the responder has no current record of revocation for that serial. It is not a statement that the certificate is valid for your use.

unknown means the responder does not know the certificate, usually because the request names an issuer that responder does not serve. It is not a revocation. It is not a clean bill of health either. It means the status could not be determined by this responder, and the client is left to decide whether to try another source.

revoked is the only one that is unambiguously bad news, and even that has a wrinkle: RFC 6960 permits a responder to return revoked for a certificate that was never issued, provided it includes the extended revoked definition extension and sets the revocation reason to certificateHold with a revocation time of January 1, 1970. If you are parsing responses by hand, do not assume revoked implies a real issuance history.

Then there is the fourth outcome, which the protocol does not call a status at all: the responder did not answer. RFC 6960 defines error responses — malformedRequest, internalError, tryLater, sigRequired, unauthorized — and states plainly that these messages are not signed. tryLater specifically indicates the service exists but is temporarily unable to respond.

This is where most homegrown probes go wrong. The HTTP request times out, the script catches the exception, logs a warning, and returns success — because the certificate itself is fine, right? The certificate may well be fine. You do not know. That is the point.

The design rule: every probe needs a third state

Every probe should return one of three things: healthy, failed, or could not determine. The third state must never be collapsed into healthy. A probe that cannot reach the OCSP responder has not established that the certificate is unrevoked. A probe that cannot reach the endpoint has not established that the certificate is valid. A probe whose own execution failed has told you nothing about the target.

This is not a stylistic preference. It is the difference between a monitor and a random number generator. The failure mode of a silent probe is identical, from the alerting system’s perspective, to a healthy endpoint: no page. You will discover the difference during the incident, which is the worst possible time.

Concretely, the three-state model means:

  • A timeout or connection error from the OCSP responder is could not determine, not healthy.
  • An OCSP unknown response is could not determine unless you have a documented policy that says otherwise for that issuer.
  • A verify invocation that fails to run — missing binary, unreadable trust store, malformed chain file — is could not determine, not a pass.
  • A verify run that completes with errors is failed, with the error code and depth attached.

Write the policy down before you write the code. What do you do with unknown? What do you do with tryLater? What do you do when the responder is reachable but its response is stale — outside the thisUpdate/nextUpdate window? If you do not decide, the default behavior is whatever your tool happens to do, which is usually to treat the absence of a clear failure as success.

A worked example of the failure this prevents

Consider a probe that does the following: fetch the chain, run verify against the local trust store, then query the OCSP responder named in the leaf’s AIA extension. If the OCSP query throws, catch it and continue. Return success if verify passed.

Now suppose the responder is down for maintenance, or the network path from your probe host to the responder is blocked by a firewall change nobody documented. The probe returns green. The certificate may in fact be revoked — you would not know, because you never got an answer. The monitor has reported health it did not observe.

Under the three-state model, the same probe returns could not determine with the responder URL and the error attached. That is a page, or at minimum a ticket, and it is the correct response: your revocation checking is broken, and you should know that before you need it.

What to log on every run

The value of a probe is not the current status. It is the record that lets you diagnose a failure six months from now without re-running against an endpoint that may have changed. Log, at minimum:

  • The chain as served: subject, issuer, serial, notBefore, notAfter, and depth for each certificate.
  • The verify result: exit status, every error number, and every depth reported.
  • The trust store used, by path and by content hash if you can manage it.
  • For revocation: the responder URL, the response status, thisUpdate, nextUpdate, and producedAt if present.
  • For any indeterminate result: the specific error — timeout, TLS failure, HTTP status, OCSP error name.

The thisUpdate/nextUpdate window is particularly worth keeping. A responder that is reachable but serving stale responses is a different problem from a responder that is unreachable, and the two require different fixes. If you only log “OCSP OK”, you cannot tell them apart later.

What this does not cover

Two things are deliberately out of scope, and it is worth being explicit about why.

Certificate Transparency. CT logs are append-only ledgers of certificates that anyone can query, and a monitor can compute an audit proof to verify that a particular certificate exists in a log. That is a valuable check — it tells you whether someone issued a certificate for your domain that you did not request. But it answers a question about the public record, not about the certificate your server is currently presenting. A CT monitor and a local endpoint probe fail in different ways and should be operated as separate concerns.

CAA records. RFC 8659 is explicit that relying parties must not use CAA records as part of certificate validation. CAA is an issuance-time control that CAs check before issuing. It is not a validation input, and a probe that reads CAA and treats it as a trust signal is doing something the specification forbids.

FAQ

Can I just use openssl verify with -crl_check and be done?

You can, and it will catch some things. But -crl_check depends on having a current CRL available, either via -CRLfile or via -crl_download against the certificate’s CDP entries. If the CRL cannot be fetched, you need to know whether the tool treated that as a failure or as a pass, and you need to know which. The three-state model applies here too: a CRL fetch failure is could not determine.

What is the difference between an OCSP unknown and a responder error?

unknown is a signed, definitive response saying the responder does not know the certificate — usually because it does not serve that issuer. A responder error is an unsigned message saying the request could not be processed. RFC 6960 lists the error types and states that error messages are not signed. They are different failure modes and should be logged differently.

Should I alert on unknown?

That is a policy decision, and you should make it deliberately. If your issuer’s responder returns unknown for certificates it should know about, that is a problem worth investigating. If it returns unknown for a certificate from a different issuer because you queried the wrong responder, that is a probe bug. Either way, treating unknown as healthy by default is the one option that is hard to defend.

Why not just check the leaf’s expiry and call it monitoring?

Because the intermediate can expire first, and because expiry is only one of the three questions. A chain with a valid leaf and an expired intermediate fails path validation. A chain with a valid leaf and a revoked intermediate fails revocation checking. Neither failure is visible from the leaf’s notAfter alone.

Does a successful verify mean the certificate is valid for my clients?

It means the chain validates against the trust store you gave it, at the time you ran it, with the revocation settings you configured. If your clients use a different trust store, a different revocation policy, or a different clock, the answer can differ. Probe with the configuration you actually care about.

The Element Tiles Don’t Spell It: Reading the ‘I Wear This Shirt Periodically’ Shirt

The shirt reads I WEAR THIS SHIRT PERIODICALLY, and beneath the sentence sit four tiles in the visual grammar of the periodic table: 53 I, 74 W, 22 Ti, 16 S. The symbols, read left to right, are I W Ti S. They are not a literal spelling of the sentence above them. That gap is the design’s actual argument, and it is worth more attention than the pun alone usually gets.

Most periodic-table jokes work by substitution. You take a word, find element symbols that approximate its letters, and let the reader do the decoding. The pleasure is in the click of recognition. This shirt does something slightly different. The sentence is already there, fully spelled out in ordinary letters. The tiles are not carrying the message; they are annotating it. The joke is not that the elements spell the sentence. The joke is that they almost do, and that the almost is the point.

What the tiles actually say

Read the tiles as a chemist would read them, symbol first, atomic number second. Iodine, tungsten, titanium, sulfur. Four elements, four symbols, four numbers. The symbols are I, W, Ti, S. Say them aloud and you get something close to “I wear this” — close enough that the eye wants to finish the sentence, and the sentence is already printed above to help it along. The design is not asking you to decode a hidden message. It is asking you to notice that the decoding is imperfect and that the imperfection is the joke.

This is a more interesting construction than a straight substitution cipher, because it depends on the reader holding two readings at once. The top line is a claim about the wearer’s habits. The bottom line is a chemical in-joke that only works if you know the symbols and also know they do not quite spell what you expect. The shirt rewards the reader who pauses. It punishes nobody who doesn’t. That is a reasonable design contract for a piece of clothing.

The word ‘periodically’ is doing two jobs

The adverb is the hinge. In ordinary English, “periodically” means from time to time, at intervals. In a chemistry context, it points at the periodic table, the arrangement of elements by atomic number and recurring properties. The shirt uses the word in both senses simultaneously, and the tiles underneath make the second sense visible. The sentence is not a chemistry claim. It is a scheduling claim that has been given a chemistry costume.

What I find worth noting is that the costume is not decorative. The tiles are specific. The numbers are real atomic numbers for those elements. The symbols are the actual symbols. The design has done the work of getting the chemistry right, then used that correctness to support a joke that is deliberately not a chemical statement. That is a different kind of care than a shirt that simply prints “Periodic Table” in a blocky font and calls it a day.

A slogan versus a usable promise

Technical readers spend a lot of time around slogans that are not promises. A status page that says “all systems operational” is a promise. A sticker that says “I break for kernel panics” is a slogan. The difference matters because slogans are cheap and promises are expensive. This shirt is firmly in the slogan category, and it is honest about that. It does not claim the wearer is a chemist, a scientist, or even particularly good at chemistry. It claims the wearer wears the shirt periodically, which is a modest and verifiable statement about laundry rotation.

That modesty is part of why the design works. The joke is not “I am a genius.” The joke is “I own a shirt and sometimes put it on.” The periodic-table tiles elevate a mundane admission into a small visual puzzle. The wearer is not asserting expertise. The wearer is asserting a schedule. In a work culture that often confuses visible enthusiasm with competence, a shirt that makes a smaller, more accurate claim is a quiet relief.

Reading the boundary between joke and claim

There is a temptation, when writing about a shirt like this, to inflate it into a statement about STEM education or the value of science communication. I do not think the artwork supports that. The design is a pun. It is a well-constructed pun with accurate element data, but it is not an argument about anything. The useful observation is narrower: the shirt distinguishes between a sentence that is spelled out and a set of symbols that almost spell it, and it lets the reader enjoy the gap. That is a specific visual move, and it is executed cleanly.

If you are the kind of person who reads config files for fun, you probably already know the difference between a message that is literally true and a message that is usefully true. This shirt is literally true in its top line and usefully funny in its bottom line. The two do not have to agree. The design knows that, and it does not try to force them together.

The shirt is available as a standard t-shirt from Quirky Cotton Collective: I Wear This Shirt Periodically Funny Chemistry Science Pun. The listing title and the observed artwork are the basis for everything above; I have not worn it, tested it, or surveyed anyone who has.

Editorial note: This publication and Quirky Cotton Collective share an owner. This column is not an independent review.

The Crossed-Out Hand Is the Whole Argument: Reading the ‘Not Friendly Do Not Touch’ Shirt

The shirt does not say stay away. It says NOT FRIENDLY in warning-sign capitals, then DO NOT TOUCH beneath it, with a yellow caution triangle and a hand crossed out by a diagonal bar. The crossed-out hand is the part I keep returning to. A hand is not a hazard in the way a wet floor or a high-voltage cabinet is a hazard. It is the ordinary instrument of greeting. The design borrows the grammar of industrial safety signage and points it at the most routine social gesture there is.

That is a more specific joke than the phrase “introvert shirt” suggests, and it is worth reading closely rather than filing under personality types.

What the sign language actually claims

Warning signs work because they compress a rule into a shape you can parse at a distance. Yellow triangle: caution. Diagonal bar over a pictogram: this action is prohibited. The format assumes a reader who is moving, possibly in a hurry, possibly not paying attention, and who needs the instruction before the situation develops. It is signage for people who will not read a paragraph.

Applied to a person, the format makes an odd but coherent claim. It says the boundary is not a mood or a preference to be negotiated in the moment. It is posted. It is legible from across a room. The wearer has done the social labor in advance so that no one has to discover the limit by crossing it.

I read the crossed-out hand as the design’s real content, not decoration. The words alone — not friendly, do not touch — could be mere grumbling. The pictogram converts grumbling into a posted rule. A hand reaching toward the wearer is the exact thing being prohibited, and the diagonal bar is the exact mechanism of prohibition. The shirt is not describing a personality. It is issuing an instruction in the only visual vocabulary that reliably survives being ignored.

The difference between a slogan and a usable promise

Most joke shirts are slogans. They assert something about the wearer and leave it there. This one is closer to a specification, and specifications are interesting because they can be evaluated.

A usable promise has three properties. It is stated in advance. It is unambiguous to a reader who has not been briefed. And it is enforceable by the person who posted it. The warning-sign format delivers the first two almost automatically. The third is where the design gets honest: a shirt cannot actually stop a hand. What it can do is remove the excuse. After the sign is read, a boundary crossed is a boundary crossed knowingly.

That is the same distinction I care about in operational writing. A runbook that says “monitor the service” is a slogan. A runbook that says “if queue_depth > 500 for five minutes, page the on-call and run this command” is a promise you can hold someone to. The difference is not tone. It is whether the statement tells a reader what to do when the situation arrives. The shirt’s lettering passes that test in the social register: it tells the reader what not to do, in advance, without requiring a conversation.

I am not claiming the design was conceived as a lesson in interface writing. I am saying the format it borrows is the format that works, and that is why the joke lands. Caution signage is one of the few visual systems most adults have already learned to obey without reading.

Why the terse version is the harder version

There is a temptation to read the shirt as antisocial. I think that misreads the mechanism. The blunt version is often the kinder one, because it replaces a slow accumulation of small signals — the closed posture, the one-word answers, the unanswered message — with a single legible statement. Ambiguity is not politeness. It is a cost transferred to the other person, who now has to guess.

The design’s economy is the point. Six words, one triangle, one barred pictogram. Nothing to interpret, nothing to negotiate. Whether that reads as hostile or as considerate depends on the reader’s position: a boundary that is never stated has to be inferred from a cold shoulder, and the posted version is easier to live with than the guesswork.

This is also why the shirt works better as an object than as a caption. A caption can be scrolled past. A sign on a chest is encountered at conversational distance, which is exactly the distance at which the rule applies.

Reading the object without overclaiming it

The listing is the Not Friendly Do Not Touch Funny Warning Sign Introvert shirt from Quirky Cotton Collective, observed live on the US marketplace on 14 October 2026 as a standard t-shirt. The visual observation is the one I have been working from: warning-sign lettering, a yellow caution triangle, and a crossed-out hand. I have not worn it, and I am not going to pretend a garment review is the right frame for a design this simple. What can be said is what the artwork does, and the artwork is unusually disciplined about doing one thing.

The restraint is worth naming. There is no second joke, no ironic sub-caption, no decorative clutter competing with the pictogram. A warning sign that hedges stops being a warning sign. The design trusts the format enough to leave it alone, and that trust is what makes the crossed-out hand read as a rule rather than a punchline.

If there is a criticism, it is that the format is so familiar it can be read too quickly — glanced at, categorized as novelty, and dismissed before the instruction registers. That is a risk inherent to borrowing signage grammar: familiarity is the source of its authority and also the reason people stop seeing it. The shirt’s best audience is the one that looks twice.

What the sign leaves out

A real caution sign usually tells you what to do instead. Authorized personnel only. Use alternate route. Keep clear. This one only prohibits. That omission is where the design is most honest about its limits: it can post a boundary, but it cannot supply the alternative. The wearer still has to be a person after the sign is read. The shirt handles the approach; it says nothing about what happens next.

I find that more interesting than a slogan would be. The crossed-out hand is not a claim that the wearer is difficult or dangerous. It is a claim that the terms of contact are posted, in advance, in a format no one can plausibly misunderstand. That is a small and specific promise, and the design keeps it.

Editorial note: This publication and Quirky Cotton Collective share an owner. This is not an independent review.

The Notification and the Nose: Reading the Doorbell-Camera Cat Shirt

The shirt does not show a cat on a porch. It shows a notification, and then it shows what the notification is about. At the top, a white rounded banner with a small bell icon carries the sentence There is motion at your front door. Below it, inside a circular, green-tinted frame, an orange tabby stares into the lens from a distance of roughly nothing. Porch siding and a light fixture sit behind the cat’s head. The composition is a stack: message first, evidence second. That order is the joke, and it is also the most interesting thing about the design.

Most novelty shirts put the punchline in the text and let the image decorate it. This one inverts the usual hierarchy. The text is the kind of sentence a doorbell camera pushes to a phone — flat, procedural, almost bureaucratic. The image is the reason that sentence exists. Read top to bottom, the shirt performs the exact sequence a homeowner experiences: the alert arrives, you open it, and the cause of the alert is a cat with its face pressed against the camera housing. The banner is not a caption. It is the notification, and the circle is the attachment.

What the green circle is doing

The circular crop with a green cast is doing specific visual work. It reads as a camera feed rather than a photograph — the tint suggests night vision or a low-light sensor, and the circle suggests a fisheye lens or an app’s thumbnail view. The cat is not posed. It is too close, slightly distorted by the lens, eyes wide in the way that animal faces go wide when a small object makes a noise. The porch siding behind it is ordinary. The light is ordinary. Nothing about the scene is dramatic except the proximity.

That proximity is the whole point. A doorbell camera exists to answer a question: who is at the door? The answer here is nobody the system was designed for. The cat has defeated the sensor by being small, warm, and curious. The shirt does not need a caption explaining this. The banner says motion; the circle shows the motion; the viewer supplies the rest.

The sentence as a boundary

There is a useful distinction between a slogan and a usable message, and this shirt sits on the right side of it. There is motion at your front door is not a slogan. It is a status line. It tells you what happened, where it happened, and nothing else. It does not tell you whether to worry. It does not tell you who or what caused the motion. It is, in the literal sense, a notification: a terse, factual push from a system that has detected a change and is passing the observation to a human for judgment.

Anyone who has run monitoring knows this shape. A good alert says what fired and where. A bad alert says everything is fine until it isn’t. The doorbell banner is the good kind — short, specific, and honest about its own limits. It does not claim the cat is a burglar. It does not claim the porch is safe. It reports motion and stops. The humor comes from the gap between that restraint and the absurdity of the cause.

I like the shirt for that reason more than for the cat itself. The cat is funny. The sentence is well-built. It is the kind of message a careful operator would write: no adjectives, no speculation, no reassurance. Just the observation and the location. The fact that the observation turns out to be a tabby with its nose on the lens is the payoff, but the discipline of the wording is what makes the payoff land.

Why the stack matters

If the design were only the cat, it would be a cute animal shirt. If it were only the banner, it would be a generic tech-humor shirt. The combination is what makes it specific. The banner establishes the system’s point of view — automated, literal, slightly officious. The circle establishes the human’s point of view — the thing the system could not classify. The shirt is a small diagram of a mismatch between what a sensor reports and what a person sees.

That mismatch is familiar to anyone who has tuned an alert threshold. You set a rule, the rule fires, and the thing that triggered it is not the thing you were worried about. A cat on a porch is a harmless version of that experience. The shirt does not pretend it is a security lesson. It is a visual joke about a notification that was technically correct and practically useless, and it works because both halves of that sentence are visible at once.

The There Is Motion At Your Front Door shirt is a standard t-shirt from Quirky Cotton Collective. The design is the banner and the circular camera view, nothing more. It does not need more. The joke is complete in two elements, and the restraint is part of the appeal.

Reading it as an off-duty object

This is not a shirt that argues for self-hosting or against it. It is not a tutorial and it is not a policy statement. It is a piece of clothing that happens to use the visual grammar of a monitoring system to make a joke about a cat. That is a legitimate thing for a technical reader to enjoy. The vocabulary of alerts, thresholds, and status lines has leaked out of the terminal and into everyday objects, and this shirt is a clean example of that leak.

The design also has a practical virtue: it is legible. The banner text is short enough to read at a glance, and the circular image is distinct enough to register from across a room. A shirt like this works best when the joke arrives before the explanation. Here it does. You see the notification, you see the cat, you get it. The order is the message.

What I keep coming back to is the sentence itself. There is motion at your front door. It is a complete thought, delivered without drama, and it leaves the interpretation to whoever reads it. That is a good way to write an alert. It is also, apparently, a good way to write a shirt.

Disclosure: This publication and Quirky Cotton Collective share an owner.

Reading DMARC Aggregate Reports Without Drowning: parsedmarc and a Ten-Minute Weekly Routine

DMARC aggregate reports are the only honest feedback channel you get about who is sending mail as your domain. They are also, by design, hostile to casual reading: one XML file per receiving organization per reporting window, gzip or zip compressed, with a schema that has drifted across a decade. RFC 7489 defines the mechanism and the report format, and it is now marked obsolete in favor of RFC 9989, RFC 9990, and RFC 9991 — which means a small operator’s inbox can contain reports in the legacy draft schema, the 1.0 schema, and the newer schema at the same time. Reading those by hand does not scale past a handful of domains, and most people who try stop after the second week.

The bottleneck is not parsing. parsedmarc already normalizes the schema for you. The bottleneck is decision fatigue: an open-ended instruction to “review the reports” has no natural stopping point, so it gets deferred until it is abandoned. The fix is a fixed weekly slot with a fixed sort, and a hard stop at the end of it. This article describes that routine, the classification heuristics behind it, and the smallest parsedmarc setup that supports it on a one-to-ten-machine estate.

What an aggregate report actually contains

RFC 7489 describes DMARC as a mechanism by which a domain owner expresses policy and receives feedback, and notes that receiver feedback “can provide valuable insight about the management of internal operations and the presence of external domain name abuse.” That is the whole promise. The aggregate report is the delivery vehicle for it.

Each report has three logical parts:

  • Report metadata — the reporting organization, a report ID, and a date range expressed as Unix timestamps. The date range is the window in which the messages were seen, not when the report was sent.
  • Published policy — the receiver’s echo of the DMARC record it found for your domain when it processed the traffic: p, sp, adkim, aspf, and (in older schemas) pct.
  • Records — one row per unique combination of source IP and authentication outcome, with a count, the aligned verdict in policy_evaluated, the header_from identifier, and the raw auth_results for SPF and DKIM.

The distinction that matters most is between policy_evaluated and auth_results. The former is the aligned DMARC verdict; the latter is the raw SPF and DKIM result before alignment is considered. A row can show auth_results/spf/result = pass while policy_evaluated/spf = fail, because SPF passed for a domain that does not align with your header_from. That single pattern explains most of the confusing rows a small operator will encounter, and it is the reason a raw pass/fail count is not a useful summary.

Two limits are worth stating plainly. Aggregate reports contain no message content, no subject lines, and no recipient addresses — they are statistical roll-ups. And DMARC itself, per RFC 7489, does not address visually similar “cousin domains” or abuse of the human-readable display name. If your concern is a lookalike domain, aggregate reports for your own domain will not show it.

Why parsedmarc, and what it actually does

parsedmarc is a Python module and CLI for parsing DMARC reports. Its documented behavior is narrow and useful: it parses aggregate reports across the legacy draft and 1.0 schemas (RFC 7489) and the RFC 9990 schema, transparently handles gzip or zip compression, and produces “consistent, normalized output, regardless of the report schema.” It can ingest from an inbox over IMAP, Microsoft Graph, or the Gmail API, and it can emit simple JSON and/or CSV, or optionally ship results to Elasticsearch, OpenSearch, Splunk, PostgreSQL, Kafka, S3, Azure Log Analytics, Graylog, syslog, or an HTTP webhook.

That feature list is longer than a small operator needs. The smallest useful configuration is IMAP ingestion plus JSON or CSV output to a local directory. The dashboard stacks are an upgrade path, not a prerequisite, and treating them as the entry point is how a ten-minute routine turns into a weekend project.

The normalized record shape is what makes the routine possible. In the documented sample output, each record carries a source object with ip_address, country, reverse_dns, base_domain, and ASN fields; a count; an alignment object with spf, dkim, and dmarc booleans; a policy_evaluated object with disposition and the aligned dkim/spf results; an identifiers object with header_from and envelope_from; and an auth_results object with the raw DKIM and SPF results. The report metadata carries begin_date and end_date, and policy_published carries the echoed policy.

Note what is not in that list: a spoofing verdict. parsedmarc normalizes and enriches; it does not classify senders for you. The classification is the operator’s job, and it is the part the routine is built around.

The ten-minute weekly loop

Run this once a week, at a fixed time, and stop when the steps are done. The point of the fixed slot is that the task has an end.

1. Confirm the window before reading anything else

Start with report_metadata.begin_date and end_date. Reports describe a past window and arrive after it closes. If you changed a DNS record this week, the reports that arrived this week may describe traffic from before the change. Drawing conclusions from a report whose window predates your change is the most common way to misread a DMARC report, and it costs ten seconds to avoid.

2. Check policy_published against what you believe you published

The receiver’s echo of your record is a free sanity check on your own DNS. If it shows p=none when you believe you published quarantine, or an alignment mode you did not intend, stop the triage and fix the record first. Everything downstream is built on the assumption that the policy in the report is the policy you meant to publish. checkdmarc is the right tool for this step: it validates SPF and DMARC DNS records, checks for authorization records on reporting addresses, and — per its documentation — warns when a DMARC record is made ineffective by sp values or by use of the pct/rf/ri tags that were removed in RFC 9989. parsedmarc tells you what receivers saw; checkdmarc tells you whether what you published is well-formed. They answer different questions.

3. Group records by source IP and count

Sort the week’s records by source.ip_address, summing count. Do this before reading individual rows. Low-count rows from unfamiliar IPs are usually noise — a single probe, a misdirected bounce, a test — and spending the ten minutes on them is how the routine dies. Weight attention by count so the time goes to the sources that actually sent mail.

4. Sort each source into one of four buckets

This is the core of the routine. The buckets are:

  1. Known and aligned. A source you recognize, with an aligned pass. Nothing to do.
  2. Known but misaligned. A source you recognize — your own mail server, a CRM, a newsletter platform, an invoicing tool — that is failing alignment. This is a configuration problem, not an attack.
  3. Forwarder. Mail you sent that a recipient or mailing list relayed. SPF breaks because the path changed; DKIM often survives.
  4. Unknown. A source you cannot account for, failing authentication. Treat as spoofing until proven otherwise.

The heuristics for sorting are working rules, not facts, and they should be stated as such:

  • Recognizable IP plus at least one raw auth_results pass suggests a legitimate sender you have not finished configuring. A real attacker does not control infrastructure that passes SPF or DKIM for your domain.
  • SPF fail with DKIM pass and aligned suggests forwarding. The forwarder’s IP is not in your SPF record, but the DKIM signature survived the relay. Because DMARC passes on either aligned mechanism, these rows pass overall.
  • Both raw checks fail or are absent, from an unrecognized IP is the strongest spoofing signal available in aggregate data. An attacker has no DKIM private key for your domain and cannot pass aligned SPF.

These are heuristics because the data does not carry intent. A recognizable IP can be a compromised legitimate sender; an unrecognized IP can be a new service someone in the organization signed up for last month. The bucket is a hypothesis to verify, not a verdict.

5. Write one action per bucket-2 and bucket-4 source

For each bucket-2 source, the action is a configuration fix: get the sender to sign DKIM as your domain, or set a custom return-path so SPF aligns. For each bucket-4 source, the action is an investigation: look up the IP, check whether anything in the organization recognizes it, and decide whether it is a forgotten service or an actual spoofing attempt. One line per source. No essays.

6. Stop

The routine is done when every bucket-2 and bucket-4 source has a line. It is not done when every source is fixed — fixes take time and belong to the following weeks. The ten-minute budget is for triage, not remediation.

A worked example, clearly hypothetical

The following rows are invented to show the sorting. They are not drawn from any real report, and the volumes are arbitrary.

Source Count Raw SPF Raw DKIM Aligned Bucket Action
Your own mail server 1,200 pass pass yes 1 None
A newsletter platform you use 4,000 pass (platform domain) pass (platform domain) no 2 Enable DKIM signing as your domain, or set a custom return-path
A mailing list relaying your mail 30 fail pass yes (DKIM) 3 None
An IP you do not recognize 900 fail none no 4 Look up the IP; investigate before acting

The point of the table is the sorting, not the numbers. The newsletter platform is the interesting case: it is legitimate, it is sending real mail, and it is failing DMARC because nothing is anchored to your domain. The fix is configuration. The unknown IP is the case that enforcement would eventually block, but only after the legitimate senders are clean.

What the routine deliberately does not do

Three exclusions keep the routine bounded.

It does not decide enforcement timing. The output of the weekly loop is an inventory and a fix list. Moving from p=none to quarantine or reject is a separate, slower decision that depends on the inventory being complete and the fixes being verified across more than one reporting window. Changing policy in the same week you are still discovering senders is how legitimate mail gets blocked.

It does not interpret forensic (RUF) reports. parsedmarc can parse them, but the documentation notes that most reporting organizations do not send forensic reports of any kind for privacy reasons, and that while aggregate reports are sent at least daily, it is normal to receive very few forensic reports. A routine built around RUF volume would be a routine that mostly does nothing. If a forensic report does arrive, treat it as a separate investigation, not part of the weekly loop.

It does not replace checkdmarc. The record validation step in the loop is a sanity check, not a full audit. checkdmarc does the full audit — SPF lookup counting, DMARC tag validation, authorization records for external reporting destinations — and it belongs in the same maintenance rotation, not inside the ten minutes.

Privacy and retention for a small VPS

Aggregate reports contain source IP addresses and the domains those sources authenticated as. They do not contain message bodies or recipient addresses, but they are still data about third parties, and a small operator storing years of them on a VPS should decide a retention period deliberately rather than by default. The practical minimum is long enough to cover a full sending cycle — monthly invoices, quarterly payroll, seasonal campaigns — so that a sender which only appears occasionally is not missed. Beyond that, the reports have diminishing value: the inventory is what you keep, not the raw files. If you archive processed mail rather than deleting it, set a retention period and enforce it.

The same logic applies to the parsed output. JSON and CSV files in a local directory are easy to back up and easy to forget. If the directory is on the same VPS as the mail server, it is covered by whatever backup routine already exists — which is either reassuring or a reason to check that routine.

FAQ

Do I need Elasticsearch or a dashboard to do this?

No. parsedmarc can emit JSON and/or CSV to a local directory, and the weekly routine works from those files. The dashboard stacks are documented options, not requirements, and for a one-to-ten-machine estate they add operational surface without changing the classification decisions.

What if I get no reports at all?

Check the record first with checkdmarc: a malformed rua value, a missing authorization record for an external reporting destination, or a policy the receiver cannot parse will all suppress reports. If the record validates and reports still do not arrive, the next question is whether anyone is sending mail as the domain at all.

Can I tell spoofing from misconfiguration with certainty?

No. Aggregate data carries source IPs and authentication results, not intent. The four-bucket sort produces hypotheses; verification — looking up the IP, asking whether anyone recognizes the service — is what turns a hypothesis into a decision. Treating the heuristic as a verdict is how legitimate senders get blocked.

Should I publish ruf= as well as rua=?

It costs nothing to publish, and a minority of receivers honor it. Do not build a routine around it. Per the parsedmarc documentation, most reporting organizations do not send forensic reports for privacy reasons, and low or zero volume is normal.

How often should the routine run?

Weekly is the point: frequent enough that a new sender is caught before it becomes a habit, infrequent enough that the task stays bounded. Daily triage is more attention than a small estate’s aggregate data justifies; monthly is long enough for a misconfigured sender to go unnoticed through a full billing cycle.

Sources

The Notebook Cover as a Costume: Reading the Composition Notebook Shirt

The first thing I notice on the Quirky Cotton Collective composition notebook shirt is not the ghost. It is the speckle. The printed rectangle is a black-and-white field of small, irregular dots — the visual texture of a recycled-paper composition book cover, the kind with the marbled look that has been standard classroom equipment for decades. A dark binding strip runs down the left edge. Inside the rectangle sits a white ruled label, the kind where a student writes their name, headed with the word COMPOSITION. Inside that label, an outlined ghost and an outlined pumpkin sit together. The whole thing is a printed notebook cover, not a physical notebook, and the shirt does not pretend otherwise.

That distinction matters more than it might seem. A costume that reproduces an object works by compression: it takes the recognizable silhouette and surface of a thing and puts it where a person is. Here the compression is unusually literal. The notebook cover is not a metaphor for a notebook; it is a drawing of one, scaled to a torso. The ghost and pumpkin are not decorations added to a school supply — they are placed inside the label, the one part of a composition book that is meant to be written on. That placement is the design’s real argument, and it is worth reading closely.

The label as the site of the joke

On an actual composition book, the white ruled label is functional. It is where ownership gets declared in handwriting. The label is the only part of the cover designed to be filled in, and the only part that changes from one book to the next. Everything else — the speckle, the binding, the printed word COMPOSITION — is identical across millions of copies.

On this shirt, the label is where the Halloween content lives. The ghost and pumpkin occupy the space that would normally hold a name. That is a small, precise substitution: instead of a student’s identity, the label carries a seasonal motif. The design does not spell out what that means, and I would be overreading to claim it means any one thing. But the visual logic is clear enough. The notebook cover is a surface for recording; the label is the designated place for a specific entry; and here the entry is a ghost and a pumpkin rather than a name. The joke, such as it is, comes from putting the seasonal content exactly where the functional content belongs.

This is different from a shirt that simply prints a slogan about teaching or a generic school supply. A slogan asserts something. This design does not assert; it arranges. The ghost and pumpkin are not captioned, not labeled, not explained. They sit inside the ruled lines of the label and let the viewer complete the association. That restraint is what makes the design readable as a costume rather than as a novelty print. The composition book is the costume; the ghost and pumpkin are the occasion.

Why the speckle carries the recognition

The black-and-white speckled field is doing most of the recognition work. Without it, the rectangle would be a plain box with a label. With it, the rectangle reads immediately as a composition book cover — the marbled surface is distinctive enough that the eye identifies it before it reads the word COMPOSITION. The dark binding strip on the left reinforces the orientation: this is a book seen from the front, with its spine on the left, exactly as it would sit on a desk.

That orientation is worth pausing on. A shirt is a vertical surface worn on a moving body. A notebook cover is a flat object usually seen on a table or held in a hand. Printing the cover upright on the torso means the wearer becomes the front face of the book. The binding strip runs down the wearer’s left side, which is where a spine would be if the wearer were the book. The design commits to that orientation fully: the label is horizontal, the ruled lines run left to right, the word COMPOSITION sits at the top of the label. Nothing is rotated or adapted for the garment. The shirt is a book cover, and the body is the book.

I am describing what the artwork shows, not claiming the design was conceived with that reading in mind. The visual evidence supports the observation that the cover is printed in its native orientation. Whether that was a deliberate joke about person-as-notebook or simply the most legible way to print a recognizable object, I cannot say from the image alone. But the effect is the same either way: the wearer is placed inside the object rather than beside it.

Group costume logic and the limits of the print

The retail title includes the phrase “Teacher Group Halloween,” and the design makes sense in that context. A composition notebook is a shared object in a classroom — every student has one, and the teacher handles them constantly. A group of teachers wearing the same printed notebook cover would read as a coordinated costume without requiring anyone to carry a prop or wear a full-body suit. The shirt does the work of the costume; the group supplies the repetition.

That is a practical advantage of the printed-cover approach. A costume that reproduces a flat object can be worn comfortably and recognized at a distance. The trade-off is that the recognition depends entirely on the viewer knowing what a composition book looks like. The speckle and the label are doing all the work, and if a viewer does not connect them to the object, the shirt reads as an abstract pattern with a ghost in it. That is not a flaw in the design; it is the nature of a reference-based costume. It rewards the viewer who recognizes the reference and does not try to explain itself to the viewer who does not.

I would not claim the shirt is a better or worse costume than any other. I have not worn it, and I have no evidence about how it reads in a classroom or at a party. What I can say is that the design is internally consistent: the cover is printed as a cover, the label is placed where a label goes, and the seasonal content sits inside the label rather than scattered across the speckle. That consistency is what makes the object legible as a costume rather than as a graphic that happens to include a notebook.

The ghost and pumpkin as a minimal seasonal signal

The ghost and pumpkin are outlined, not filled. They sit inside the ruled lines of the label, which means they are constrained by the label’s borders. That constraint is visually interesting: the seasonal content is contained within the functional space, rather than overflowing it. The ghost and pumpkin do not spill onto the speckled field. They stay where a name would go.

This is a restrained choice. A less careful design might have scattered ghosts and pumpkins across the whole cover, turning the notebook into a Halloween pattern. That would have lost the notebook-cover reading, because the cover would no longer look like a cover — it would look like a Halloween print with a notebook border. By keeping the seasonal content inside the label, the design preserves the object’s identity. The notebook is still a notebook; the Halloween content is an entry on it.

I do not know whether the outlined style was chosen for print reasons, aesthetic reasons, or both. What I can observe is that the outlines keep the ghost and pumpkin visually subordinate to the label’s structure. The label’s ruled lines remain visible, the word COMPOSITION remains the header, and the seasonal content reads as something written or drawn inside the label rather than as a replacement for it. That hierarchy is what makes the design work as a costume: the object comes first, the occasion second.

What the shirt is and is not

It is a printed garment showing a composition notebook cover with a ghost and pumpkin in the label. It is not a physical notebook, not a costume kit, and not a claim about teaching or Halloween beyond the visual reference. The design does not cure anything, improve anything, or demonstrate anything about the wearer. It is a recognizable object transferred to a shirt, and its interest comes from how carefully that transfer is executed.

For readers who spend their working lives around structured surfaces — terminals, config files, dashboards, logs — the composition book is a familiar cousin. It is a surface with a fixed format and a designated place for a specific entry. The shirt puts a ghost in that place. That is a small visual joke, and it does not need to be more than that. The design’s discipline is what makes it readable: the cover is a cover, the label is a label, and the seasonal content knows its boundaries.

Editorial note: This publication and Quirky Cotton Collective share an owner.

The Loudest Possible Way to Say Nothing: Reading the ‘meh.’ Shirt

The garment preview shows a dark shirt with four characters across the chest: meh. Lowercase, bold, white, and large enough to read from across a room. There is no illustration, no animal, no secondary slogan, no gradient. The period is present and it is doing work. That is the entire design, and the design is the argument.

I want to take the typography seriously rather than treat the shirt as a joke delivery vehicle. Because the joke is not in the word. The word is nearly contentless. The joke, if there is one, lives in the mismatch between how the word is set and what the word means. meh is the sound of a shrug. It is the verbal equivalent of a flat line with a slight downward tilt. And yet here it is at display size, in heavy lowercase, centered, declarative, with terminal punctuation. The shirt is not whispering indifference. It is announcing it.

What the artwork actually shows

From the native garment preview: large bold white lowercase lettering reading meh. on a dark shirt. No animal illustration is present. No pride variant, no flag, no mascot. The type is sans-serif, the weight is heavy, the letters are tightly set, and the period sits flush after the h. The contrast between white type and dark ground is high, which is what makes the word legible at distance. Nothing else competes for attention.

I am describing the preview, not a shirt I have worn. What I can say from the image is structural: the design has one element, and that element is scaled to dominate the chest. There is no visual hierarchy to parse. The reading order is the word, then the period, then nothing.

The period is the whole design decision

Strip the period and you have meh — an interjection, a trailing-off, a word that invites the sentence to continue and then declines. Add the period and the word becomes a sentence. A complete thought. A verdict with a full stop after it.

This is the kind of distinction that matters in technical writing and in operational communication generally. A status message that says degraded is a state. A status message that says degraded. is a conclusion. The period converts an observation into a judgment. On a shirt, that is funny because the judgment is so small. The punctuation promises finality and the word delivers apathy. The form is emphatic; the content is a shrug. That tension is the design.

I would not claim the designer reasoned this way. I am reading the artifact. But the artifact does contain the tension, whether or not anyone intended it, and the tension is why the shirt works as more than a novelty.

Loud type, quiet message

There is a recognizable pattern in how technical people signal boundaries. The message is short. The delivery is flat. The affect is deliberately reduced. A one-word reply in a chat thread. A terse commit message. A monitoring alert that says only what changed and nothing abouthow anyone feels about it. The register is not rudeness; it is economy. Words cost attention, and attention is the scarce resource.

The meh. shirt takes that register and scales it up to billboard size. That is the joke, and it is also a small piece of self-aware commentary. The person wearing it is not actually indifferent — they bought a shirt about indifference, which is a commitment. The performance of apathy requires effort. The large type admits the effort.

This is where I part company with the usual reading of shirts like this. The standard take is that meh is introvert humor, a low-stakes way to say I have no strong opinion and I would like to keep it that way. That reading is fine as far as it goes, but it misses the typographic joke. The shirt is not quiet. It is the loudest possible way to say nothing. The wearer is not hiding; they are making a public statement about not making public statements. The contradiction is the point.

Slogans versus usable promises

I spend a lot of working hours around status language: alert titles, runbook headings, incident summaries, the one-line descriptions that end up in a dashboard. The useful ones share a property. They commit to something checkable. Disk usage above 90% on /var is a usable promise; you can verify it. Something feels off is not.

meh. is the anti-promise. It commits to nothing. It is the sentence you write when you have been asked for an opinion and you have decided that the cost of having one exceeds the benefit. As a shirt, that is a mood. As an operational message, it is a failure mode. The same four characters can be either, depending on context, and the shirt is interesting because it puts the failure mode on a chest and dares you to read it as a personality.

I do not think the shirt is making a point about monitoring or incident response. I think it is making a point about the wearer’s willingness to engage, and the point lands because the typography refuses to be modest about it. But the overlap with status language is real, and it is why the design reads as more than a gag to someone who spends their day writing terse messages that have to be true.

Why the absence of an animal matters

The observation is explicit: no animal illustration is present. That sounds like a negative fact, but it is a design fact. A lot of novelty shirts in this register lean on a mascot — a sloth, a cat, a cartoon face — to carry the joke visually so the words can stay small. This design does not. The word has to carry everything, which is why it is set so large. The absence of an illustration is what forces the typography to be the illustration.

That is a harder design to pull off than it looks. A single word at display size has nowhere to hide. The letterforms have to be clean, the spacing has to be even, the contrast has to hold. From the preview, the type is doing that work. The word is legible and the composition is stable. Whether the print holds up over time is a different question, and not one the preview can answer.

Who this is for, and what it is not

If you are looking for a meh shirt that reads as dry humor rather than a costume, this one is unusually disciplined. It has one idea and it executes it without clutter. The dark ground and white type make it wearable in the same way a band shirt with a single logo is wearable: the design is the message and the message is short.

What it is not: a statement about introversion that any psychologist would endorse, a productivity tool, or a substitute for actually telling someone you need space. The shirt is a visual joke about a verbal shrug. It does not cure anything and it does not improve anything. It is a piece of cloth with four characters and a period on it, and the period is the best part.

I keep coming back to the punctuation. A word that means I decline to elaborate has been given the one mark that says this sentence is finished. The shirt is a closed loop. It says what it says and then it stops. In a world of status messages that trail off into hedges and caveats, there is something almost restful about a garment that commits to being done.

Editorial note: This publication and Quirky Cotton Collective share an owner.

The Dashed Circle and the Solid Ball: Reading the Physics Diagram on the ‘I Had Potential’ Shirt

The shirt shows a diagram before it shows a joke. In white capitals across the chest: I HAD POTENTIAL. Below the words, a curved ramp descends from an upper-left ledge to a lower-right flat. At the top of the ramp, on the ledge, sits a dashed empty circle. At the bottom, resting on the flat, a solid white ball. The ramp is a curve, not a straight diagonal. The dashed circle is not a ghost or a faded print; it is a drawn outline, the kind of mark a physics diagram uses for a position that is not occupied.

That is the whole design. No arrows, no axes, no numbers, no equation. The joke depends on the reader supplying the missing labels, and the argument of this piece is that the shirt is more interesting when you notice how carefully it withholds them. It is a diagram of potential in the physics sense, and a sentence about potential in the personal sense, and the drawing keeps the two from collapsing into each other.

What the drawing actually shows

Read literally, the image is a before-and-after with the after left out. The dashed circle marks where the ball was, or where a ball could be. The solid ball marks where it is now. The curved ramp connects the two positions. In a physics diagram, a dashed outline often indicates a previous position, an initial state, or a location that is defined but not currently occupied. A solid object indicates the present state. The ramp is the path between them.

What the shirt does not show is the moment of transition. There is no motion blur, no arrow, no ball at the midpoint of the curve. The ball is already at the bottom. The dashed circle is already empty. The sentence above says I had potential, past tense, and the drawing agrees: the potential was up there, on the ledge, and the ball is down here now.

This is a more disciplined piece of design than a straight diagonal ramp would have been. A straight ramp reads as a wedge, a simple incline, a one-step drop. The curve reads as a path with a shape, the kind of track a ball would actually follow under gravity if the surface were smooth. The flat lower ledge matters too: the ball has come to rest, not merely passed through. The composition is stable. Nothing is still moving.

Potential is a loaded word in two registers

In physics, potential energy is energy associated with position. A ball at the top of a ramp has more of it than the same ball at the bottom. The word potential in the sentence is doing the same work: it names a capacity that depends on where you are, not on what you are. The shirt’s joke is that the ball has changed position and therefore changed potential, and the sentence treats that as a personal history.

I want to be careful here. The shirt is not a physics lesson, and the drawing is not a worked problem. It does not specify the height of the ramp, the mass of the ball, or the acceleration involved. It does not claim that the ball’s potential has been converted into anything in particular. What it does is borrow the visual grammar of a diagram — dashed outline, solid object, curved path, two levels — and use that grammar to make a sentence about a person. The borrowing is the joke, and the joke works because the grammar is recognizable even when the labels are missing.

The personal register is equally loaded. I had potential is a sentence people say about themselves when they are being hard on themselves, or when they are being funny about being hard on themselves. It is past tense. It implies a comparison between an earlier assessment and a present one. The shirt does not say who did the assessing, or when, or whether the assessment was correct. It just puts the sentence above a drawing of a ball that has rolled down a ramp and stopped.

The dashed circle is the most important mark on the shirt

The empty dashed circle at the upper left is easy to overlook. It is white on white, an outline rather than a fill, and it sits at the edge of the composition. But it is doing most of the work. Without it, the shirt would show a ball at the bottom of a ramp and the words I had potential. That would be a statement about a ball. With the dashed circle, the shirt shows a position that is marked but unoccupied, and the sentence becomes a statement about a difference between two states.

The dashed circle also keeps the shirt from being purely self-deprecating. A solid ball at the bottom of a ramp is just a ball that fell. A dashed circle at the top is a reminder that the top existed, that the ball was there, that the potential was real enough to be drawn. The shirt is not saying the potential was imaginary. It is saying the potential was a position, and the ball is no longer in it.

That distinction matters for how the joke lands. If the dashed circle were absent, the shirt would read as a simple before-and-after with the before cropped out. With the dashed circle present, the before is still on the shirt, drawn in outline, and the viewer has to reconcile the two marks. The design asks you to hold both states in view at once. That is a small cognitive task, and it is the reason the shirt is more than a slogan.

What the shirt is not claiming

It is worth being explicit about the limits of the drawing. The shirt does not demonstrate a physical result. It does not show a measurement, a calculation, or a prediction. It does not claim that the ball’s potential energy has been dissipated in a particular way, or that the ramp is frictionless, or that the ball is a point mass. Those are assumptions a physics problem would state; the shirt leaves them out because it is not a physics problem. It is a drawing that borrows the look of one.

Nor does the shirt make a claim about the wearer’s actual abilities, history, or prospects. The sentence is a quotation of a familiar self-assessment, not a diagnosis. The drawing is a visual pun on the word potential, not an argument about anyone’s life. I read the shirt as a joke about the gap between an earlier state and a present one, and the joke depends on the viewer recognizing both the physics convention and the personal sentence. It does not require the viewer to believe either one literally.

This is the difference between a slogan and a usable promise. A slogan asserts something and asks you to agree. A diagram, even a joke diagram, shows a relationship and asks you to read it. The shirt is closer to the second. It gives you two marks and a path and a sentence, and it lets you supply the rest. That is why it rewards a second look more than most text-only shirts do.

Why the minimalist treatment helps

The shirt is white text and white line art on a dark garment, with no additional decoration. There is no border, no background panel, no secondary joke. The restraint is not just aesthetic; it keeps the diagram legible as a diagram. A busier design would turn the ramp and ball into an illustration. The plain treatment keeps them as marks, which is what a diagram is.

The capitalization of I HAD POTENTIAL is also doing work. All caps give the sentence the flat, declarative tone of a label on a chart. It is not shouted; it is stated. The words sit above the drawing like a caption, and the drawing sits below like the figure the caption refers to. The shirt reads top to bottom as caption then figure, which is the order in which you would encounter them in a printed paper. That is a small thing, but it is the kind of small thing that makes the joke feel deliberate rather than assembled.

The curved ramp is the other deliberate choice. A straight diagonal would have been easier to draw and easier to read as a generic incline. The curve gives the path a direction and a shape, and it makes the lower ledge feel like a resting place rather than a stopping point. The ball is not caught mid-roll. It is at rest. The shirt is a still image of a completed process, and the dashed circle is the only trace of the earlier state.

Reading it as a work-culture object

For technical readers, the shirt’s subject matter is familiar even if the drawing is not. The language of potential is everywhere in engineering and operations: potential incidents, potential outages, potential savings, potential risk. Those phrases usually describe something that has not happened yet, and they carry an implicit instruction to prevent it or capture it. The shirt’s sentence is the past tense of that language. It describes a potential that is no longer available, and it does so without saying whether that is a failure or a relief.

That ambiguity is what makes the shirt usable as an off-duty object. It does not commit to a mood. It could be worn by someone who is genuinely rueful about a path not taken, or by someone who finds the whole idea of potential faintly ridiculous, or by someone who just likes diagrams. The drawing holds the sentence at a slight angle, so the wearer is not forced into a single reading. That is a more sophisticated piece of design than a shirt that tells you exactly what to feel.

The shirt also has a quiet relationship to the way technical people talk about systems. A dashed outline is a common convention for something that is planned, expected, or no longer present. A solid mark is something that is actually there. The shirt uses that convention to make a point about a person, and the point is not that the person is a system. It is that the visual language of states and transitions is flexible enough to carry a joke about a life. The flexibility is the interesting part, not the joke alone.

The object itself

The shirt is sold as the I Had Potential Funny Physics Pun Sarcastic Minimalist Quote by Quirky Cotton Collective. The design is the white caption and the white ramp-and-ball diagram described above. If you want to see the current listing, it is here: I Had Potential shirt. I am not going to pretend the shirt does anything beyond what the drawing shows. It is a garment with a diagram on it, and the diagram is worth reading closely.

The design works because the drawing and the sentence are not redundant. The sentence says I had potential. The drawing shows a ball that has moved from a dashed position to a solid one. Neither element explains the other completely. The viewer has to connect them, and that connection is the joke. A shirt that simply printed the sentence would be a slogan. A shirt that simply printed the diagram would be an illustration. The combination is a small argument about how to read a change of state, and it is more effective for being understated.

Editorial note: This publication and Quirky Cotton Collective share an owner. This is not an independent review.

The Two Meanings of Rooting: Reading the Smiling Vegetables on the ‘Let’s Root for Each Other’ Shirt

The artwork on this shirt is a closed loop of text and image. The words LET’S ROOT FOR EACH OTHER AND WATCH EACH OTHER GROW run around the outside, and inside that ring sit four smiling root vegetables — a carrot, a radish, a beet, and an onion — each drawn with its roots visible in the soil. The pun is doing double duty, and the design knows it. To root for someone is to support them. To root, for a plant, is to establish underground. The shirt puts both meanings in the same sentence and lets the vegetables carry the second one.

What interests me is not the joke itself but the structure of the promise it makes. “Let’s root for each other and watch each other grow” sounds like a statement of mutual support, and it is. But it also contains an expectation: growth. The watching is not passive. The sentence implies that the rooting is instrumental — we root so that growth happens, and we watch to confirm it. That is a very different thing from support without conditions, and it is worth reading closely because the same slippage shows up in how technical teams talk about culture.

The vegetables are doing the work

Look at the four vegetables. They are not generic plants. They are root vegetables, which means the edible part is underground. The visible leaves and stems are not the point; the roots are. The design draws the roots explicitly, in soil, so the viewer sees what is normally hidden. That is a small visual argument: the growth that matters is the growth you cannot see from above.

I read the smiling faces as a deliberate softening. Root vegetables pulled from the ground are lumpy and dirty. These are cartoonish and cheerful, arranged in a ring, each one distinct in shape. The variety matters because the sentence is about mutual support, and mutual support only means something if the parties are not identical. Four identical carrots would make the point weaker. Four different vegetables, each with its own root system, make the ring feel like a group rather than a set.

The text surrounds them completely. There is no gap in the ring, no escape from the sentence. That is a design choice, and I think it is the right one for the pun: the words enclose the vegetables the way soil encloses roots. The sentence is the environment. The vegetables are what grows inside it.

Rooting versus watching

The sentence has two verbs and they are not symmetrical. Rooting is an activity you do for someone. Watching is an activity you do to someone, or at least in their direction. The first is supportive; the second is observational. Put them in sequence and you get a model of encouragement that includes surveillance. I do not think the shirt intends this reading, but the grammar supports it, and that is exactly the kind of ambiguity that makes a slogan worth wearing or worth arguing with.

In technical work, this distinction matters. A team that says “we support each other” is making a claim about behavior. A team that says “we watch each other grow” is making a claim about visibility and measurement. The first is about what you do when someone is stuck. The second is about what you do when someone is progressing. The shirt collapses them into one line, which is charming on a garden-themed garment and slightly ominous in a performance review.

My reading is that the shirt is on the charming side. The smiling vegetables pull the sentence toward warmth. The roots in soil pull it toward patience. Growth, in a garden, is slow and mostly underground, and you cannot hurry it by watching. That is a useful corrective to the way growth gets talked about in workplaces, where it is often treated as a deliverable with a deadline. The shirt does not say “let’s root for each other and measure each other’s growth.” It says watch. Watching is what you do when you are not in control of the timeline.

What the shirt is not saying

It is worth being precise about the limits of the design. The artwork does not claim that mutual support causes growth, or that watching produces anything. It is a pun, not a thesis. The vegetables are drawn with roots visible in soil, which is a visual convention for root vegetables, not a botanical diagram. I am not making a claim about gardening practice or soil science. I am reading the composition: text around the outside, four distinct smiling vegetables inside, roots drawn explicitly, faces softened.

The shirt is a Quirky Cotton Collective design, and the pun is the kind of thing that works best when it is not over-explained. If you have to say “root, as in cheer, but also root, as in the part of the plant,” the joke is already dead. The design trusts the viewer to get it from the vegetables. That trust is part of why it works.

The usable promise

Here is the argument I want to make about this shirt: it is a better model of team culture than most explicit culture statements because it is specific about what support looks like and vague about what growth looks like. Support is the rooting — the visible, above-ground activity of cheering someone on. Growth is the underground part — the roots, the slow work, the thing you cannot see from the surface. The shirt puts the support on the outside and the growth on the inside, and that is the right order. You can root for someone without knowing exactly what their growth will look like. You cannot watch them grow without first giving them soil.

That is a more usable promise than “we value growth” or “we support each other,” because it separates the two activities and assigns them different positions. The rooting is the ring of text. The growth is the vegetables. The watching is the relationship between them. None of this is a management framework, and I would not want it to be. It is a shirt with a pun on it. But the pun is well-built, and well-built puns are worth reading closely because they show you how a sentence holds together when it is doing two things at once.

The four vegetables are smiling. That is the last detail and maybe the most important one. They are not competing for the same soil. They are not arranged in a hierarchy. They are in a ring, each with its own roots, and the sentence around them applies to all of them equally. That is what mutual support looks like when it is drawn rather than declared.

Disclosure: This publication and Quirky Cotton Collective share an owner.

Posts navigation

1 2 3
Scroll to top