Skip to content

fix: stop quoting docker's pull noise as the cause of a Unity abort - #293

Merged
frostebite merged 2 commits into
mainfrom
fix/unity-abort-message-noise
Sep 20, 2026
Merged

frostebite merged 2 commits into
mainfrom
fix/unity-abort-message-noise

Conversation

@frostebite

@frostebite frostebite commented Sep 20, 2026

Copy link
Copy Markdown
Member

Changes

The Unity abort message (0a4aabe)

  • UnityBatchmodeFailure.describe() now takes originalMessage as optional, and omits the Original error: section entirely when the caller has nothing to add.
  • Docker.run stops passing its stderr. System.run has already streamed that stream live above, so repeating it lost nothing and only asserted it was the cause.
  • HostRunner is deliberately unchanged: it still passes its message, because on its --silent path the streamed copy does not exist and the quoted text is the only one.
  • Regression test in docker.test.ts now asserts the message does not contain Unable to find image; a second test covers the originalMessage-omitted shape directly.

When Unity aborts batchmode, the reason lives in stdout and the surrounding log. What got appended underneath it was docker run's own stderr, which opens with the benign pre-pull status line:

Unity aborted before completing: Scripts have compiler errors.

This is a failure inside the Unity Editor itself ...
The full Unity log is above.

Original error:
Unable to find image 'unityci/editor:6000.3.23f1-base-3' locally

The actual cause is the first line. The last three read as a version-pull failure, and that is exactly how two Mirror maintainers read it on MirrorNetworking/Mirror#4128 — they went hunting a 6000.3.23f1 problem while Scripts have compiler errors. sat in the same message, two lines above. Surfacing the abort reason (#288) was doing its job; the appended stderr was burying it.

No fix is needed for the failure itself — that is a compile error in the PR's own code (Assembly.GetLoadedAssemblyPath, a Unity 6.4+ API called under a UNITY_6000_0_OR_NEWER guard). This is about the message not pointing people the wrong way.

The install smoke tests (5fdab58)

Added because this PR's own CI hit both problems, and they are the same shape as the one above — a check failing for a reason its message never names.

  • The macOS job 403'd on api.github.com while the ubuntu job passed beside it, running the same install.sh against the same endpoint in the same minute. install.sh resolves latest through the API and authenticates only when handed a token; unauthenticated is 60 requests/hour per IP, and Actions runners share IPs across unrelated repos and orgs. The script was written to be given one and says so in its own comment — the workflow never passed it. Both smoke steps, plus the root-wrapper steps that default to latest and make the same call, now pass GH_TOKEN (the pattern already used throughout orchestrator-integrity.yml).
  • The retry loop reported every failure as "likely the latest release's binaries are still uploading". A 403 rate limit is not that, and re-running the identical unauthenticated request 30 seconds later cannot clear it — it spent all five attempts and 2.5 minutes asserting a cause it had no way to know. It now points at the error that actually occurred.

install.sh treats an empty token as no token, so a fork PR (no secrets, read-only github.token) degrades to today's unauthenticated behaviour rather than failing. Verified end-to-end locally: GH_TOKEN=… bash scripts/install.sh latest <dir> resolves v0.1.69, downloads, and checksum-verifies. actionlint clean.

Checklist

  • Read the contribution guide and accept the code of conduct
  • Readme (updated or not needed)
  • Tests (added, updated or not needed)

🤖 Generated with Claude Code

When Unity aborts batchmode, Docker.run passes the reason from stdout and
then appended `docker run`'s own stderr under an "Original error:" heading.
That stderr opens with the benign pre-pull "Unable to find image ...
locally" status line, so the message read as an editor failing to load a
version and buried the actual cause two lines above it:

  Unity aborted before completing: Scripts have compiler errors.
  ...
  Original error:
  Unable to find image 'unityci/editor:...' locally

Two Mirror maintainers read it exactly that way on Mirror#4128 - they went
looking for a 6000.3.23f1 problem while "Scripts have compiler errors." sat
in the same message.

System.run already streams that stderr live, so repeating it loses nothing.
describe() now takes originalMessage as optional and omits the section when
the caller has nothing to add. HostRunner keeps passing it: on its --silent
path the streamed copy does not exist, so the quoted text is the only one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4a26fa4e-15ab-4e99-b4fc-f10300169298

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd4666 and 0a4aabe.

📒 Files selected for processing (4)
  • src/model/docker.test.ts
  • src/model/docker.ts
  • src/model/unity/unity-batchmode-failure.test.ts
  • src/model/unity/unity-batchmode-failure.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change makes the original error message optional for Unity batchmode failures. Docker no longer appends streamed stderr to the failure description. Regression tests verify that Unity's compiler-error reason remains visible without Docker pull-progress noise.

Changes

Unity batchmode reporting

Layer / File(s) Summary
Optional batchmode failure description
src/model/unity/unity-batchmode-failure.ts, src/model/unity/unity-batchmode-failure.test.ts
UnityBatchmodeFailure.describe now accepts an optional original message. Without it, the output omits the Original error: section. Tests verify this behavior.
Docker error integration
src/model/docker.ts, src/model/docker.test.ts
Docker.run calls describe without Docker stderr. The regression test checks that the Unity compiler-error reason is present and Unable to find image is absent.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Low

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing Docker pull-status output from being reported as the cause of a Unity abort.
Description check ✅ Passed The description includes the required Changes and Checklist sections, explains the implementation and regression tests, and marks the relevant checklist items complete. It also includes additional ins…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Two defects in the release-download smoke tests, both hit live on PR #293:

The macOS job 403'd on api.github.com while the ubuntu job passed beside it,
running the same install.sh against the same endpoint in the same minute.
install.sh resolves "latest" through the API and authenticates only when
handed a token - unauthenticated is 60 requests/hour per IP, and Actions
runners share IPs across unrelated repos and orgs. The script was written to
be given one, and says so in its own comment; the workflow never passed it.
Both smoke steps, and the root-wrapper steps that default to "latest" and
hit the same call, now pass GH_TOKEN - the pattern already used throughout
orchestrator-integrity.yml. install.sh treats an empty token as no token, so
a fork PR (no secrets, read-only github.token) degrades to today's behaviour
rather than failing.

The retry loop reported every failure the same way - "likely the latest
release's binaries are still uploading". A 403 rate limit is not that, and
re-running the identical unauthenticated request 30 seconds later cannot
clear it, so it spent all five attempts and 2.5 minutes asserting a cause it
had no way to know. That is the same misdiagnosis this branch's Unity abort
message fix exists to remove: point at the error that actually occurred
instead of substituting a guess for it.

Verified end-to-end: GH_TOKEN=... bash scripts/install.sh latest <dir>
resolves v0.1.69, downloads, and checksum-verifies. actionlint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@frostebite
frostebite merged commit a2302b3 into main Sep 20, 2026
29 checks passed
@frostebite
frostebite deleted the fix/unity-abort-message-noise branch September 20, 2026 19:59
frostebite added a commit that referenced this pull request Sep 21, 2026
* fix: stop quoting docker's pull noise as the cause of a Unity abort

When Unity aborts batchmode, Docker.run passes the reason from stdout and
then appended `docker run`'s own stderr under an "Original error:" heading.
That stderr opens with the benign pre-pull "Unable to find image ...
locally" status line, so the message read as an editor failing to load a
version and buried the actual cause two lines above it:

  Unity aborted before completing: Scripts have compiler errors.
  ...
  Original error:
  Unable to find image 'unityci/editor:...' locally

Two Mirror maintainers read it exactly that way on Mirror#4128 - they went
looking for a 6000.3.23f1 problem while "Scripts have compiler errors." sat
in the same message.

System.run already streams that stderr live, so repeating it loses nothing.
describe() now takes originalMessage as optional and omits the section when
the caller has nothing to add. HostRunner keeps passing it: on its --silent
path the streamed copy does not exist, so the quoted text is the only one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci: stop the install smoke tests failing for the wrong reason

Two defects in the release-download smoke tests, both hit live on PR #293:

The macOS job 403'd on api.github.com while the ubuntu job passed beside it,
running the same install.sh against the same endpoint in the same minute.
install.sh resolves "latest" through the API and authenticates only when
handed a token - unauthenticated is 60 requests/hour per IP, and Actions
runners share IPs across unrelated repos and orgs. The script was written to
be given one, and says so in its own comment; the workflow never passed it.
Both smoke steps, and the root-wrapper steps that default to "latest" and
hit the same call, now pass GH_TOKEN - the pattern already used throughout
orchestrator-integrity.yml. install.sh treats an empty token as no token, so
a fork PR (no secrets, read-only github.token) degrades to today's behaviour
rather than failing.

The retry loop reported every failure the same way - "likely the latest
release's binaries are still uploading". A 403 rate limit is not that, and
re-running the identical unauthenticated request 30 seconds later cannot
clear it, so it spent all five attempts and 2.5 minutes asserting a cause it
had no way to know. That is the same misdiagnosis this branch's Unity abort
message fix exists to remove: point at the error that actually occurred
instead of substituting a guess for it.

Verified end-to-end: GH_TOKEN=... bash scripts/install.sh latest <dir>
resolves v0.1.69, downloads, and checksum-verifies. actionlint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci: reproduce the Mirror#4128 Unity abort message end to end

A container aborts Unity, docker leads its own stderr with the benign
"Unable to find image '...' locally" pre-pull notice, and the CLI's final
error used to append that stderr under an "Original error:" heading. The
message then read as an editor failing to load a Unity version, while
Unity's real reason - a genuine script compile error - sat two lines above
it (#288).

#293 fixed the message and src/model/docker.test.ts asserts it, but that
test hard-codes BOTH streams onto its fake error, so it stays green if
Docker.run reads the wrong stream or System.run folds them together. That
plumbing is precisely what broke, and nothing in CI ran a real `docker run`
through it. This adds that, and grades what the user is actually shown.

- New unity-abort-message job: an aborting-editor stub (no Unity, no
  licence, no multi-GB pull), driven through both CLI routes because they
  reach stdout differently - build.sh passes `-logfile /dev/stdout`, while
  test.sh writes the log to a file and cats it after capturing the exit
  code. Both graded by scripts/assert-unity-abort-message.sh.
- Wired into smoke-gate.needs, so a failure there actually fails the gate.
- scripts/test-assert-unity-abort-message.sh drives the grader in both
  directions against hand-written logs - including the pre-#293 message it
  exists to reject - because its two "must not contain" assertions can only
  ever pass vacuously on a run that died early.
- A stream-split case in system.integration.test.ts, against a real child
  process rather than the mocked System.run.
- A comment pinning the `silent` coupling Docker.run's catch block rests on
  ("System.run has already streamed it live above"), which holds only while
  options.silent stays falsy. No behaviour change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci: address review on the abort-message reproduction

Declare UNITY_VERSION through each step's env rather than interpolating the
workflow_dispatch input into the script body, where ${{ }} expands into shell
source before bash sees it. Clean the grader's temp file from an EXIT trap so
an interrupt cannot leave it behind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.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.

1 participant