Two related problems in the onboarding path, both found by running scripts/onboard_member.py on a scratch copy of the repo.
Part 1: add_member_to_lab_manual has no duplicate guard
scripts/onboard_member.py writes a new member into three places. Two of those writes check whether the member is already present; the third does not.
add_to_spreadsheet (scripts/onboard_member.py:855) checks for an existing row before appending.
add_to_cv (scripts/onboard_member.py:965-973) checks for an existing entry before appending.
add_member_to_lab_manual (scripts/parse_lab_manual.py:125-170) does neither. It locates the appropriate \begin{itemize} section for the member's role and inserts a new \item unconditionally.
Reproduction
- Copy the repo to a scratch directory so nothing committed is touched.
- Run the onboarding flow for a new member (e.g. an undergrad RA) and let it write to
lab_manual.tex.
- Run it again with exactly the same inputs. Run it a third time.
grep the member's name in the relevant itemize block of lab_manual.tex.
Expected
The second and third runs are no-ops for lab_manual.tex — the member appears exactly once, matching the behavior of the spreadsheet and CV writers.
Actual
The count of \item entries for that member goes 1 → 2 → 3 across the two re-runs. Every re-run appends another identical \item. The spreadsheet and CV, run in the same invocation, correctly stay at one entry each — so a re-run leaves the three sources of truth disagreeing with each other.
This has happened in production
The lab-manual history already contains a cleanup commit for exactly this failure mode: 80db6d1, "Remove duplicate undergrad entries from re-onboarding." Re-running onboarding is a normal thing to do (a typo in an email address, an interrupted run, a member whose role changed), so this will keep recurring until the guard exists.
Suggested fix
Give add_member_to_lab_manual the same pre-insert check its siblings have: before inserting the \item, scan the target itemize block for the member's name (normalized the same way the CV and spreadsheet checks normalize it) and return early — reporting "already present," not "added" — if found. This makes the whole onboarding script idempotent, which is what its callers already assume.
Part 2: build_cv.py reports success without verifying the PDF
compile_pdf and validate_output in scripts/build_cv.py report a successful CV build based on the LaTeX invocation completing, without confirming that the PDF on disk is actually new and complete. Two ways this goes wrong:
- Stale PDF. If the LaTeX run fails but a PDF from a previous build is still sitting on disk, the check passes and the build is reported as successful. The onboarding script then tells the operator the CV was rebuilt when the file still reflects the previous state — the new member is silently missing.
- Truncated PDF. A partial LaTeX failure can leave a short, valid-enough PDF in place. The observed trigger here is the Monaco font failure mode: when the font isn't resolvable, the run degrades and the emitted PDF is well under the real page count, but nothing in
validate_output notices.
The net effect is the same in both cases: onboard_member.py prints a success message for a CV build it has not verified, and the operator has no signal that anything went wrong.
Note on current state
The committed JRM_CV.pdf is currently fine — 14 pages, correct content. This is a missing guardrail, not a broken artifact. Nothing needs to be regenerated; the request is to stop reporting unverified success.
Suggested fix
In validate_output:
- Capture the PDF's mtime (or hash) before the LaTeX run and require it to change afterward, so a stale file can never pass.
- Assert a minimum page count (the CV is ~14 pages; anything in the low single digits means a degraded run) and a plausible minimum file size.
- Treat a nonzero LaTeX exit status, or a
Font ... not found / missing-font warning in the log, as a hard failure rather than something to fall through.
- Propagate the real result up to
onboard_member.py so the operator sees a failure when the build failed.
Two related problems in the onboarding path, both found by running
scripts/onboard_member.pyon a scratch copy of the repo.Part 1:
add_member_to_lab_manualhas no duplicate guardscripts/onboard_member.pywrites a new member into three places. Two of those writes check whether the member is already present; the third does not.add_to_spreadsheet(scripts/onboard_member.py:855) checks for an existing row before appending.add_to_cv(scripts/onboard_member.py:965-973) checks for an existing entry before appending.add_member_to_lab_manual(scripts/parse_lab_manual.py:125-170) does neither. It locates the appropriate\begin{itemize}section for the member's role and inserts a new\itemunconditionally.Reproduction
lab_manual.tex.grepthe member's name in the relevantitemizeblock oflab_manual.tex.Expected
The second and third runs are no-ops for
lab_manual.tex— the member appears exactly once, matching the behavior of the spreadsheet and CV writers.Actual
The count of
\itementries for that member goes 1 → 2 → 3 across the two re-runs. Every re-run appends another identical\item. The spreadsheet and CV, run in the same invocation, correctly stay at one entry each — so a re-run leaves the three sources of truth disagreeing with each other.This has happened in production
The lab-manual history already contains a cleanup commit for exactly this failure mode:
80db6d1, "Remove duplicate undergrad entries from re-onboarding." Re-running onboarding is a normal thing to do (a typo in an email address, an interrupted run, a member whose role changed), so this will keep recurring until the guard exists.Suggested fix
Give
add_member_to_lab_manualthe same pre-insert check its siblings have: before inserting the\item, scan the targetitemizeblock for the member's name (normalized the same way the CV and spreadsheet checks normalize it) and return early — reporting "already present," not "added" — if found. This makes the whole onboarding script idempotent, which is what its callers already assume.Part 2:
build_cv.pyreports success without verifying the PDFcompile_pdfandvalidate_outputinscripts/build_cv.pyreport a successful CV build based on the LaTeX invocation completing, without confirming that the PDF on disk is actually new and complete. Two ways this goes wrong:validate_outputnotices.The net effect is the same in both cases:
onboard_member.pyprints a success message for a CV build it has not verified, and the operator has no signal that anything went wrong.Note on current state
The committed
JRM_CV.pdfis currently fine — 14 pages, correct content. This is a missing guardrail, not a broken artifact. Nothing needs to be regenerated; the request is to stop reporting unverified success.Suggested fix
In
validate_output:Font ... not found/ missing-font warning in the log, as a hard failure rather than something to fall through.onboard_member.pyso the operator sees a failure when the build failed.