DEV Community

Cover image for Three tools said my resume was clean. One of them was not installed.
Erik Hill
Erik Hill

Posted on

Three tools said my resume was clean. One of them was not installed.

Today I audited my own public claims and found a false one. My resume said the Android game I shipped, Tap Dodge Rush, was built with "Kotlin / Jetpack Compose". The app has never contained a line of Compose. The build file says so in my own words:

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}
Enter fullscreen mode Exit fullscreen mode
// The game is a custom View painting to a Canvas, driven by an Activity with
// an XML layout. Compose came from the project template and was never used.
Enter fullscreen mode Exit fullscreen mode

The claim was falsifiable only by me. That repo is private, so for months the one person who could check it was the person who wrote it. That is most of why it sat there.

Fixing the HTML and the markdown took a minute. Then I went to check the PDFs, and that is where the real story is.

Three tools, three clean results

$ for f in Erik_Hill_Resume*.pdf; do
    printf '%s %s\n' "$(pdftotext "$f" - 2>/dev/null | grep -ci jetpack)" "$f"
  done
0 Erik_Hill_Resume.pdf
0 Erik_Hill_Resume_DRAFT.pdf
0 Erik_Hill_Resume_VARIANT_A.pdf
0 Erik_Hill_Resume_VARIANT_B.pdf
Enter fullscreen mode Exit fullscreen mode

Clean. For a second opinion I ran a small extractor I had written earlier that pulls the PDF content streams, inflates them with zlib, and searches the literal text. Zero hits. Third pass, the blunt one:

$ strings Erik_Hill_Resume.pdf | grep -ci jetpack
0
Enter fullscreen mode Exit fullscreen mode

Three independent tools, three clean results. I was one command away from calling it done.

The check that broke it

Before closing it out I searched for a string that has to be in the file.

$ pdftotext Erik_Hill_Resume.pdf - 2>/dev/null | grep -ci erik
0
Enter fullscreen mode Exit fullscreen mode

Zero hits for "erik" in Erik Hill's resume. That cannot be true.

$ command -v pdftotext
$ pdftotext Erik_Hill_Resume.pdf -
zsh: command not found: pdftotext
Enter fullscreen mode Exit fullscreen mode

pdftotext was never installed on this machine. In a pipeline the error goes to stderr, grep reads an empty stdin, and grep honestly reports zero matches in nothing at all. The exit status I was reading belonged to grep. Every clean result in that sweep was a measurement of the empty string.

I run sweeps like this with an agent driving the shell, and the agent reported the same clean result. It was reading the same zero I was. The tooling did not fail quietly because it was an agent. It failed quietly because a pipe swallows stderr and nobody asked the pipeline to prove it could find anything.

The trap inside the fix

Here is the part I did not expect. strings also passes a positive control:

$ strings Erik_Hill_Resume.pdf | grep -i erik
/URI (https://linkedin.com/in/erik-hill-98895575)>>
/URI (https://erikhill.dev/)>>
Enter fullscreen mode Exit fullscreen mode

Two hits. A green control, on a tool that cannot read a word of the body text. Those are link annotations, which PDF stores uncompressed, while the body text sits in Flate-compressed content streams that no byte search will ever reach.

So the control passed in a layer the target does not live in. A positive control only proves the tool can read the layer the control sits in.

The zlib extractor failed for a third reason: these PDFs use subsetted fonts and hex strings, so scanning for literal parenthesized text finds nothing. That file has 27 streams, all 27 inflate cleanly, zero literal-paren text operators and 4721 hex-string ones.

The tool that actually reads a PDF here is pypdf, in a throwaway venv:

$ python3 -c "
from pypdf import PdfReader
t=''.join((p.extract_text() or '') for p in PdfReader('Erik_Hill_Resume.pdf.bak-20260825-1859').pages)
print(len(t), t.lower().count('erik'), t.lower().count('jetpack'))"
4812 5 1
Enter fullscreen mode Exit fullscreen mode

That is a stale copy that still carries the claim, so the transcript is reproducible today. Nine PDFs had it, including the two copies in ~/Downloads that get attached to applications and the one my site serves.

The two files every sweep missed

An adversarial pass afterward found two more, and they are the best part. Both are real PDFs whose names do not end in .pdf, so every *.pdf glob had skipped them:

$ find ~/Desktop ~/Downloads ~/.Trash ~/.claude -type f ! -name '*.pdf' |
  while read -r f; do
    [ "$(head -c 5 "$f" 2>/dev/null)" = '%PDF-' ] && echo "$f"
  done
.../.Trash/Erik_Hill_Resume.pdf.bak-20260825-1859
.../uploads/Erik_Hill_Resume.pdf.old-0719
Enter fullscreen mode Exit fullscreen mode

The second sits in the uploads directory that LinkedIn Easy Apply pulls from. A stale resume, one filename character away from invisible, in the exact folder an application form reaches into.

A second checkout of the same Android app is also still on disk with the full Compose stack in its build file, so a future grep that strays one directory sideways will conclude the app really is a Compose app.

The fix, and one number

The line now reads "Kotlin / Java, Android Views + Canvas, AdMob". Kotlin alone would have replaced a false claim with a misleading one: of 1204 lines in the main source sets, 710 are Java and 494 are Kotlin. The live page and the PDF it serves both carry the corrected line, checked with curl and pypdf rather than trust.

Limits

pypdf is not a universal answer either. It returns nothing on a scanned PDF, which needs OCR, and it will hand you the same confident zero. A magic-byte sweep is slower than a glob, which is why nobody runs one until a glob has already lied to them.

This is one finding from a wider audit of my own published claims. I am not listing the rest yet, because several are still being verified, and shipping a half-verified claim audit would be the same mistake in a new costume.

The rule I am left with: a search that returns nothing is not evidence of absence until you have shown the search can return something, using a control that lives in the same layer as the thing you are hunting.

So run one tonight. Take the sweep command you actually trust, over a format you actually search, and point it at a string you know is in the body of that file. If it comes back zero, which layer of the file was your tool reading?

I build evaluation infrastructure for LLM systems and I am looking for work. contact@erikhill.dev

Top comments (0)