Skip to content

fix(hostlock): export a declared owner to the wrapped command - #1968

Closed
justinchuby wants to merge 2 commits into
mainfrom
seb/hostlock-run-owner
Closed

justinchuby wants to merge 2 commits into
mainfrom
seb/hostlock-run-owner

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #1929.

The defect

hostlock.sh run --owner leon -- <bench> acquires the lock as leon and then runs the command without telling it. --owner is a shell variable, invisible across the fork. A benchmark that reads the lock therefore cannot tell its own parent's declaration from a co-tenant's, and prints:

host_lock=held:leon      # not mine:leon

held: is deliberately not a protected label. So the obvious invocation was the one that silently produced the weaker answer, and the only way to get mine: was the environment form — which worked by inheritance, incidentally, not by design. scripts/ort_ab/README.md documented around it; it now documents what the code does.

The fix

Export HOSTLOCK_OWNER="$OWNER" for the child in cmd_run — only when the owner was declared, by --owner or by HOSTLOCK_OWNER in the environment.

The $USER default is deliberately not exported. Every agent on this box is the same unix user, so a child told to answer to $USER would claim a co-tenant's lock as its own. A false mine: is worse than a missing one: it reads as protected. The reader in #1925 has no $USER fallback for exactly this reason, and exporting one from run would have reintroduced it through the back door. The lock file still records the default, so acquire is unchanged.

OWNER cannot answer "was this declared?" — by the time anything reads it, the $USER default is indistinguishable from an explicit --owner "$USER". Each source is therefore checked at its origin: the environment directly, and --owner through OWNER_DECLARED, which is initialised in the defaults block and set by the parse loop.

The second commit exists because review falsified the first

The first version read ${OWNER_DECLARED:-0} and initialised that name nowhere, with a comment arguing that this "keeps this self-contained". Bash imports inherited environment variables as shell variables, so the flag was answerable from the environment: OWNER_DECLARED=1 hostlock.sh run -- child, with no --owner and no HOSTLOCK_OWNER, exported the $USER default, and the child then matched a lock it had never claimed. The mechanism meant to prevent the false mine: reintroduced it. The comment was the defect's own justification, which is why it is gone rather than edited.

Review also found the reader's own rustdoc (field_from_env) still telling readers that run does not export the owner — the doc comment attached to the function whose meaning this change alters — and that the probes' ${VAR:-...} sat under a comment claiming it distinguishes exported-empty from never-exported, which the colon form does not do. Both fixed.

Evidence

Seven cells in hostlock_test.sh. Rather than assume they cover the defect, each was shown to fail under a defect of its own. Every arm below is a full suite run on an isolated copy:

arm result
baseline 321/321
never export (the original defect) kills 4
export unconditionally, $USER included kills 2
scrub HOSTLOCK_OWNER for the child kills 5
drop the OWNER_DECLARED initialisation kills 1 — the new cell, and nothing else

Every one of the seven cells is killed by at least one arm.

The environment cell is not killed by the first arm: an exported variable is inherited whether run re-exports it or not. It is killed by the third, and it is annotated in place with what it does and does not exclude — an assertion whose defect nobody identified is indistinguishable from one that cannot fail. Same class as the latched-OnceLock A/B in #1736 and the compile_fail doctest that passed on a typo in #1950.

The load-bearing cell compares the exported name against the name in the lock, not merely that the variable is set: the reader decides mine: by comparing those two, so an assertion on presence alone would pass with them disagreeing.

Nested inheritance (a wrapped script taking a second lock) is asserted deliberately, on a separate lock directory — a nested acquire on the same lock is contention, not inheritance, and would have tested the wrong thing.

Every cell controls HOSTLOCK_OWNER explicitly (env -u or an explicit value). The agents that run this suite export that variable, so an ambient value would otherwise decide these cells instead of the code under test.

Validation

  • bash scripts/hostlock_test.sh → 321/321, count pin 314 → 321.
  • shellcheck scripts/hostlock.sh scripts/hostlock_test.sh → clean, exit 0. The four SC2016 suppressions are load-bearing (stripping them yields exactly four info findings and a non-zero exit) and none masks an unrelated warning.
  • RUSTDOCFLAGS="-D warnings" cargo doc -p onnx-runtime-hostmon --no-deps → clean; cargo test -p onnx-runtime-hostmon → green.
  • Host lock conformance is path-filtered to these files, so this PR triggers it.

Not verified

No benchmark has yet been run under hostlock.sh run --owner X to observe a host_lock=mine:X row end to end — the host has been held by another agent. The equality that produces that label is asserted here at the shell level (the child reads the real lock and compares) and at the Rust level in #1950's child probe, but the two halves have not been joined in one live run.

Coordination

Touches scripts/hostlock.sh and scripts/hostlock_test.sh, which #1926 (gaff) also modifies. My hunks are a new helper above cmd_run, the "$@" & line inside it, two lines in the defaults/parse region, and a new test section — no overlap with #1926's changes except the assertion-count pin, which is a one-line mechanical resolution in whichever order they land.

`run` acquired the lock as OWNER and then never told the command about it.
A harness that reads the lock therefore saw a holder it could not attribute
and labelled its own protected run UNPROTECTED: `run --owner leon` produced
`host_lock=held:leon`, never `mine:leon`. The one state the field exists to
certify was unreachable by the one invocation that earns it, and the reader
was right both times -- `held:` is the honest answer when the child has been
told nothing.

`--owner` was a shell variable, invisible across the fork; only the
environment form worked, and only incidentally, by inheritance. The README
documented around it. It now says what the code does instead.

Only a DECLARED owner is exported -- `--owner`, or `HOSTLOCK_OWNER` in the
environment. The `$USER` default is not, and that asymmetry is the point:
every agent on this box is the same unix user, so a child told to answer to
`$USER` would claim a co-tenant's lock as its own. A false `mine:` is worse
than a missing one, because it reads as protected. The lock file still
records the default, so `acquire` is unchanged.

`OWNER` cannot distinguish the two sources by the time anything reads it, so
each is checked at its origin: the environment directly, and `--owner` via
OWNER_DECLARED set in the parse loop.

Six assertions, each shown to fail under its own defect rather than assumed
to cover one:

  * never export (the defect being fixed) kills four of the six;
  * export unconditionally, including the `$USER` default, kills exactly the
    one cell that holds that asymmetry down, and nothing else;
  * scrub the variable for the child kills five, including the environment
    cell -- which the first mutant does NOT kill, since an exported variable
    is inherited whether `run` re-exports it or not. That cell is annotated
    with what it does and does not exclude, because an assertion whose defect
    nobody identified is indistinguishable from one that cannot fail.

The load-bearing cell compares the exported name against the name written in
the lock, not merely that the variable exists: the reader says `mine:` by
comparing those two, so an assertion on presence alone would pass with them
disagreeing. Nested inheritance is asserted deliberately rather than left
incidental, on a separate lock directory -- a nested acquire on the same lock
is contention, not inheritance.

Suite: 320/320 with the assertion-count pin updated; shellcheck clean.

Closes #1929.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.84%. Comparing base (2155595) to head (bebbf2c).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1968      +/-   ##
==========================================
+ Coverage   80.21%   80.84%   +0.63%     
==========================================
  Files         406      423      +17     
  Lines      188213   207425   +19212     
  Branches   188213   207425   +19212     
==========================================
+ Hits       150974   167695   +16721     
- Misses      31842    34083    +2241     
- Partials     5397     5647     +250     
Flag Coverage Δ
cli-ort-windows 72.10% <ø> (?)
mlas 85.33% <ø> (?)
offline 81.00% <ø> (+0.79%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 68 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@justinchuby
justinchuby marked this pull request as ready for review August 24, 2026 11:12
@justinchuby justinchuby reopened this Aug 24, 2026
Review of the first commit falsified its headline claim. `run_owner_is_declared`
read `${OWNER_DECLARED:-0}` and nothing ever assigned that name, so bash's
import of inherited variables made it answerable from the environment: a caller
exporting `OWNER_DECLARED=1` with no `--owner` and no `HOSTLOCK_OWNER` got the
`$USER` default exported, and the child then matched a lock it had never
claimed. That is precisely the false `mine:` the change exists to prevent,
reintroduced by the mechanism meant to prevent it.

The comment that argued for reading it with a default -- "keeps this
self-contained" -- was the defect's own justification, which is why it is gone
rather than edited. `OWNER_DECLARED=0` now sits in the defaults block with
every other option variable, and the read is unguarded, so `set -u` would catch
a future path that skipped it.

Asserted by a seventh cell, and the assertion is attributed: removing the
initialisation kills that cell and nothing else (320/321), so it is testing the
severance and not something incidental.

Also from review:

  * `field_from_env`'s rustdoc still told readers `run` does not export the
    owner. It is the doc comment attached to the function whose meaning this
    change alters, so it would have misled exactly the reader who most needed
    it. Repo-wide grep finds no other copy.
  * The probes used `${HOSTLOCK_OWNER:-<unset>}` under a comment claiming it
    distinguishes exported-empty from never-exported. The colon form reports
    both as unset. Switched to the no-colon form, which does what the comment
    says.

Full mutation table, each arm a complete suite run:

  * baseline                                       321/321
  * never export                                   kills 4
  * export unconditionally, $USER included         kills 2
  * scrub HOSTLOCK_OWNER for the child             kills 5
  * drop the OWNER_DECLARED initialisation         kills 1 (the new cell)

Every one of the seven cells is killed by at least one arm.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Owner Author

Closing this as superseded by #1967 (8d6377f00), which landed the same fix while I was building this one, and landed it first.

I did not check for an existing PR on #1929 before starting — I checked the issue, which was open, and assumed that meant unclaimed. It does not, and that is my error to carry: gh pr list --search 1929 would have cost one command and saved a full branch. Recording it here rather than quietly deleting the branch, because the cost was real (three full mutation runs of a suite that already had a better version of the same cells).

#1967 is the better implementation, and I checked that rather than assuming it

Its export is one branch on OWNER_DECLARED, and OWNER_DECLARED is initialised in the defaults block. Mine read ${OWNER_DECLARED:-0} and initialised it nowhere, with a comment arguing that this "keeps it self-contained" — which an adversarial review then falsified: bash imports inherited environment variables as shell variables, so OWNER_DECLARED=1 hostlock.sh run -- child, with no flag and no HOSTLOCK_OWNER, exported the $USER default and the child matched a lock it had never claimed. My guard against the false mine: reintroduced the false mine:. #1967 never had that hole.

Its test cells are also stronger than mine in two places I want to name, because I would otherwise be tempted to re-propose them:

  • an explicit anti-vacuity cell for the default path (but the lock still records that defaulted owner), which catches the version of the fix that stops defaulting altogether — mine folded that into one compound assertion;
  • the precedence cell reasons about foreign: rather than held:, which is the sharper failure: with --owner winning for the lock file but the child keeping a stale inherited name, the reader reports somebody else's row, not merely an unattributed one.

Its structural pin (that the export lives in the one command with a child) covers a mutation mine did not test at all: relocating the export into cmd_acquire is behaviourally invisible, because cmd_run calls it as a function in the same process.

What I verified about it before closing

I checked that its OWNER_DECLARED=0 line is test-covered rather than merely present — a guard nobody can delete is worth more than a guard nobody has tested. Deleting that line on main and running the full suite:

  • clean environment: 4 red (set -u turns the unguarded read into a hard error);
  • with OWNER_DECLARED=1 inherited: 1 red — an owner defaulted from the unix user is NOT exported.

Covered in both directions. So the one cell I could have contributed is redundant, and I am not proposing it.

What survives

Exactly one thing, now in #1981: field_from_env's rustdoc still tells readers that run does not export --owner. #1967 updated scripts/ort_ab/README.md but not the doc comment on the function that decides mine: versus held:. Repo-wide grep says it is the last copy.

One diagnostic worth keeping

This PR sat for forty minutes with no Actions runs at all — no github-actions check suite was ever created for its head SHA, while other branches dispatched normally. Close/reopen did not help; neither did a force-push to a fresh SHA. The cause was that the branch conflicted with main once #1967 landed in the same files: GitHub cannot compute the merge ref, so it creates no pull_request runs. From gh pr checks that is indistinguishable from a backlogged queue — both say "no checks reported". The tell is gh api repos/{owner}/{repo}/commits/{sha}/check-suites: third-party app suites appear, the github-actions one does not.

@justinchuby
justinchuby deleted the seb/hostlock-run-owner branch August 24, 2026 12:05
justinchuby added a commit that referenced this pull request Aug 24, 2026
One doc comment. `crates/onnx-runtime-hostmon/src/hostlock.rs`, on
`field_from_env`:

> Note also that `hostlock.sh run` does not export `--owner`, so passing
the flag alone leaves a child unable to recognise its own parent's lock;
that reports `Held`, which is unattributed and unprotected, and is the
honest answer.

#1967 (`8d6377f00`) made that false. It is the doc comment on the
function that decides `mine:` versus `held:`, so the reader it misleads
is precisely the one deciding whether a benchmark row can be trusted —
and it steers them away from `run --owner X`, which is now the
invocation that works.

Repo-wide grep finds no other copy: #1967 updated
`scripts/ort_ab/README.md`, and this was the only sentence it missed.

The replacement states the asymmetry too, since a reader will otherwise
re-derive it: a **declared** owner (`--owner`, or `HOSTLOCK_OWNER`
already in the environment) is exported; an owner defaulted from `$USER`
is not, because every agent here is the same unix user and a false
`mine:` reads as protected. A run that declared nothing still reports
`Held`.

## How I found it, and what I checked

I had been fixing #1929 in parallel and did not notice #1967 until my
branch stopped getting CI (see below). Reviewing that PR against mine,
this was the one thing its diff did not cover. I also checked the two
adjacent claims in mine that #1967 might have missed, and it had not
missed them:

- Its `OWNER_DECLARED` is initialised in the defaults block, so an
inherited environment variable of that name cannot stand in for the
flag. My first attempt read `${OWNER_DECLARED:-0}` and initialised it
nowhere, which let `OWNER_DECLARED=1 hostlock.sh run -- child` export
the `$USER` default — the exact false `mine:` the change exists to
prevent. #1967 does not have that hole.
- I checked that its guard is *test-covered* rather than merely present,
by deleting the `OWNER_DECLARED=0` line on `main` and running the full
suite twice: **4 red** in a clean environment (`set -u` catches the
unguarded read), and **1 red** with `OWNER_DECLARED=1` inherited (`an
owner defaulted from the unix user is NOT exported`). Covered in both
directions, so there is nothing for me to add there.

## Validation

- `RUSTDOCFLAGS="-D warnings" cargo doc -p onnx-runtime-hostmon
--no-deps` — clean, and the intra-doc link still resolves.
- `cargo test -p onnx-runtime-hostmon` — green including doctests;
`cargo fmt --all --check` clean.

## Unrelated, but worth recording for whoever hits it next

My #1968 sat for forty minutes with **no Actions runs at all** — not
queued, not failed: no GitHub Actions check suite was ever created for
the head SHA, while other branches dispatched normally. Close/reopen did
not help, and neither did a force-push to a fresh SHA. The cause was
that the branch **conflicted with `main`** (because #1967 had landed in
the same files): GitHub cannot compute the merge ref, so it creates no
`pull_request` runs. A conflicted PR and a backlogged CI queue look
identical from `gh pr checks`, which says "no checks reported" for both.
The tell is `gh api repos/{o}/{r}/commits/{sha}/check-suites` —
third-party app suites appear, the `github-actions` one does not.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hostlock: run --owner X does not export HOSTLOCK_OWNER, so the child cannot recognise its own lock

1 participant