Skip to content

feat: add Rez PATH-shim queue environment with tool auto-discovery - #270

Merged
crowecawcaw merged 13 commits into
aws-deadline:mainlinefrom
crowecawcaw:rez-path-shim
Jul 30, 2026
Merged

feat: add Rez PATH-shim queue environment with tool auto-discovery#270
crowecawcaw merged 13 commits into
aws-deadline:mainlinefrom
crowecawcaw:rez-path-shim

Conversation

@crowecawcaw

Copy link
Copy Markdown
Contributor

Fixes: no issue yet — opening as a draft per CONTRIBUTING's "talk with us first" guidance, happy to file one if you'd prefer to track it there

What was the problem/requirement? (What/Why)

A customer reported that Rez does not work well with queue environments, because a queue environment runs in a single subprocess and the render command only receives environment variables harvested from it and replayed.

That is accurate, and it is structural rather than a bug. A queue environment action runs in its own subprocess, so the only channel into later actions is openjd_env: NAME=value directives. The existing rez_queue_env.yaml therefore activates a context, diffs the environment before and after, and replays the difference. Anything that is not a name-value pair does not survive.

A Rez alias is the clearest casualty. Rez implements it as an exported shell function, which Bash exports under a name like BASH_FUNC_launch%% with a multi-line value. The session runtime rejects that assignment outright:

openjd_env: "BASH_FUNC_demoalias%%=() {  demorender --via-alias \"$@\"\n}"
  -- ERROR: Failed to parse environment variable assignment.

So the alias is gone before any task runs. Ordered PATH edits are similarly fragile: the variable is restored, but a later environment that also edits PATH can reorder it.

What was the solution? (How)

A queue environment that wraps each task's command instead of exporting variables to it.

onEnter resolves the requested packages once, saves the context to a .rxt file in the session directory, asks Rez which executables those packages provide, and writes one shim per tool with the shim directory prepended to PATH:

#!/usr/bin/env bash
exec rez env --input "$REZ_CONTEXT_FILE" --shell bash -- "<tool>" "$@"

Job templates keep calling tools by bare name, such as command: mayapy, so each call re-enters the saved context in its own shell and Rez applies the full context inside the task's own process. Job bundles need no changes.

Tool names come from rez context -t on the saved context, so there is no hard-coded list of DCC executables. RezExtraTools covers commands a package provides without declaring them in its tools list.

This is a workaround for a specification gap, not a permanent design. RFC0008: Environment Wrap Actions proposes onWrapTaskRun, which does this properly; once it ships in the worker agent, the wrap hook replaces the shim directory and the PATH manipulation entirely. The README says so explicitly so nobody mistakes this for the intended long-term shape.

What is the impact of this change?

Additive. Three new files under queue_environments/rez_shim/, no changes to existing samples:

File Purpose
rez_queue_env_shim.yaml The queue environment to deploy on a farm
rez_demo_setup_queue_env.yaml Test scaffolding: installs Rez and builds a demo package
demo_job_bundle/template.yaml Verification job

The existing rez_queue_env.yaml is untouched and remains the right choice when packages only set variables, and the only choice on Windows.

Known tradeoffs, all documented in the README:

  • Only bare command names are intercepted; an absolute path in a template bypasses the shims.
  • Linux and macOS workers only. The shims are POSIX scripts relying on a shebang, so the environment fails immediately on Windows with a message pointing at rez_queue_env.yaml. I deliberately did not claim Windows support, since it is not tested.
  • Each task pays a context re-entry, mitigated by Rez's resolve cache.
  • The demo's package repository is created outside the session directory and persists on the worker; documented under cleanup.

How was this change tested?

Locally with openjd run, and end to end on a Deadline Cloud farm with a Linux service-managed fleet (on-demand, 2–4 vCPU). All templates pass openjd check, and python3 scripts/validate_repository.py passes.

The demo job proves its own thesis rather than asserting it. VerifyEnvironment runs three fidelity checks and fails the task on regression:

Check State under test Under harvest-and-replay
1 A plain variable Survives
2 A Rez alias Lost, rejected by the runtime
3 A PATH prepend shipping its own sort Depends on environment order

Farm run, on the worker under job-user:

--------- Entering Environment: RezDemoSetup
Created demo Rez package 'demotool-1.0.0' in /tmp/rez-demo-repository
--------- Entering Environment: Rez
Created 3 Rez tool shims in /sessions/session-.../rez-shims:
   democonvert
   demorender
   demosleep
=== DEMOTOOL_VERSION as seen by the session (expected UNSET) ===
DEMOTOOL_VERSION=UNSET
=== DEMOTOOL_VERSION as seen inside the tool (expected 1.0.0) ===
demorender: DEMOTOOL_VERSION=1.0.0
=== Check 1: plain variable reaches the tool ===
PASS: variable visible inside the tool
=== Check 2: Rez alias survives into the task ===
PASS: alias is callable
=== Check 3: package PATH prepend shadows the system command ===
PASS: package command shadows the system one
All 3 environment fidelity checks passed.

UNSET in the session with 1.0.0 inside the tool is the core result: the variable was never harvested and replayed, Rez applied it inside the task's own process.

Cancelation was verified too, since it is the obvious risk with a wrapper. Rez runs the tool in a shell of its own, so the tree is shimrez env → shell → tool rather than flat, but signals still propagate. Canceling the CancelThroughShim step on the farm:

demosleep: pid=39289 sleeping for 600s
Canceling subprocess 39247 via notify then terminate method
INTERRUPT: Sending signal "term" to process 39247
demosleep: caught SIGTERM, exiting
Process pid 39247 exited with code: 1

Task ended CANCELED with no orphans, so an application with its own signal handler gets to shut down cleanly.

Not tested: Windows (unsupported by design, guarded with a clear failure), and macOS workers on a real fleet — the macOS path was exercised locally only.

Was this change documented?

Yes. One README for the whole set at queue_environments/rez_shim/README.md, covering when to choose this over rez_queue_env.yaml, why the simpler sample cannot cover those cases, deployment, parameters, tradeoffs, the upstream RFC, and cleanup. The queue_environments index table points at the directory, matching how nested collections are handled elsewhere in the repo.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

The existing rez_queue_env.yaml resolves Rez in a subshell during onEnter and
then replays a before/after environment-variable diff via openjd_env. That
flattening loses shell functions, aliases and ordered PATH edits, which is a
known limitation for Rez users.

This adds a queue environment that instead writes a small shim per Rez tool
into the session directory and prepends it to PATH. Job templates keep calling
tools by bare name, and each invocation re-enters the resolved Rez context in
its own real shell.

Tool names come from 'rez context -t' on the saved .rxt context, so no
hard-coded list of DCC executables is needed. The RezExtraTools parameter
covers commands a package provides but does not declare.

Also adds a self-contained demo variant that installs Rez and builds a demo
package in the session directory, plus a job bundle that verifies a tool's
environment is visible inside the tool but never leaked into the session.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
Add the two new queue environments to the queue_environments index table with a
section explaining why the shim approach exists and its tradeoffs, add the demo
job bundle to the job_bundles index, and give the bundle a README following the
repository sample template.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
The demo queue environment duplicated the whole shim mechanism, so the sample
exercised a copy rather than the real environment and the two could drift.

Replace it with rez_demo_setup_queue_env.yaml, which only installs Rez and
builds a demo package. Attaching it before rez_queue_env_shim.yaml lets the demo
use the real shim environment unmodified.

Also remove Windows support from the shim environment. The shims are POSIX shell
scripts relying on a shebang, which does not work on Windows, and it was never
tested there. It now fails with a clear message pointing at rez_queue_env.yaml
instead of silently writing shims that cannot run.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
The demo package only set environment variables, which the existing
rez_queue_env.yaml carries correctly, so the sample did not demonstrate the
problem it exists to explain.

Add a Rez alias and a PATH prepend that shadows a system command to the demo
package, and turn the verify step into three fidelity checks that fail the task
on regression. Under rez_queue_env.yaml the session runtime rejects the alias
assignment outright, which is the failure the shim environment avoids.

Also capture command output before matching instead of piping into 'grep -q',
which closes the pipe on first match and trips SIGPIPE under pipefail.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
State plainly who should choose the shim environment and why, replacing prose
that described the mechanism before naming the problem. Reference the upstream
Environment Wrap Actions RFC, which supersedes this workaround once it ships.

Correct queue and fleet terminology: queues are platform agnostic, so the Linux
and macOS constraint belongs to the fleet's workers. Drop the demo setup
environment from the index table and link it from the shim section instead,
since it is test scaffolding rather than a deployable sample.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
The shim environment, its test scaffolding, and the verification job are not
independently useful, but they were split across queue_environments/ and
job_bundles/ with a README each.

Move all three under queue_environments/rez_shim/ and replace the two READMEs
with one covering the whole set. The queue_environments and job_bundles index
tables now point at the directory rather than listing the pieces separately,
matching how nested collections are handled elsewhere in the repository.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
The tradeoff list claimed cancelation was untested and asserted that 'exec'
keeps the process tree flat. The second half was wrong: Rez runs the tool in a
shell of its own, so the tree is shim -> rez env -> shell -> tool.

Signal delivery works anyway. Add a long-running demosleep tool that reports
the signal it catches, plus a CancelThroughShim step to trigger it, and replace
the untested-tradeoff bullet with the verified behavior. Confirmed on a Linux
service-managed fleet worker: canceling the step logs 'INTERRUPT: Sending signal
term' followed by 'demosleep: caught SIGTERM, exiting', with no orphans.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
@github-actions github-actions Bot added the waiting-on-maintainers Waiting on the maintainers to review. label Jul 30, 2026
Replace 'ships' with the specific verb, drop three uses of 'therefore', split a
semicolon into two sentences, restructure a three-verb series, and name the noun
behind a standalone 'This is'.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
Comment thread queue_environments/rez_shim/rez_queue_env_shim.yaml Outdated
A shim that re-invoked its tool by bare name relied on Rez rebuilding PATH from
the resolved context and dropping the shim directory. That holds by default, but
a farm whose Rez config lists PATH in parent_variables keeps the shim directory
ahead of the package's own bin directory, so the shim found itself and forked
until the worker ran out of processes. Reproduced with REZ_PARENT_VARIABLES=PATH:
60 processes in 15 seconds.

Resolve each tool with 'command -v' inside the context when the shim is written
and exec that absolute path. Tools resolving back into the shim directory, or
unresolvable in the context, now get no shim and are reported at startup instead
of failing confusingly at task time.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
@crowecawcaw

This comment was marked as outdated.

Comment thread queue_environments/rez_shim/demo_job_bundle/template.yaml Outdated
Checks 2 and 3 read the resolved context directly with 'rez env --input', so
they verified the .rxt file could be re-entered rather than that the shims and
PATH injection worked. Deleting the PATH injection from the environment left
both passing, and only check 1 caught the regression.

Add a demoprobe tool that reports the alias and 'sort' resolution from inside a
correctly applied context. Because it is a declared Rez tool it gets a shim, so
calling it by bare name makes both checks depend on the shim mechanism end to
end. With the PATH injection removed all three checks now fail.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
@crowecawcaw

This comment was marked as outdated.

@crowecawcaw
crowecawcaw marked this pull request as ready for review July 30, 2026 18:10
@crowecawcaw
crowecawcaw requested a review from a team as a code owner July 30, 2026 18:10
@crowecawcaw crowecawcaw changed the title feat: add Rez PATH-shim queue environment with tool auto-discovery feat: add Rez PATH-shim queue environment with tool auto-discovery Jul 30, 2026
@crowecawcaw crowecawcaw changed the title feat: add Rez PATH-shim queue environment with tool auto-discovery feat: add Rez PATH-shim queue environment with tool auto-discovery Jul 30, 2026
Comment thread queue_environments/rez_shim/rez_queue_env_shim.yaml Outdated
The comment told users to set a Windows repository path, carried over from
rez_queue_env.yaml. This environment defines only LINUX_ and MACOS_ variables
and fails immediately on Windows workers.

Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
@crowecawcaw

This comment was marked as outdated.

@crowecawcaw
crowecawcaw enabled auto-merge (squash) July 30, 2026 20:21
start, end = int(start_s), int(end_s)
else:
start = end = int(args.frame_range)
start_s, end_s = args.frame_range.split("-")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the single-frame branch makes frame_range.split("-") crash on a single-value FrameRange.

FrameRange is a free-form LINE_EDIT STRING param and is also fed to the step as an OpenJD task range: "{{Param.FrameRange}}", which happily accepts a bare integer (e.g. FrameRange=5 to render one test frame). With this change, "5".split("-") returns ["5"] and the unpack start_s, end_s = args.frame_range.split("-") raises ValueError: not enough values to unpack. The task range stays valid, so OpenJD schedules the frame and the script then dies with a cryptic error instead of the previous graceful start = end = int(...).

The same removal in encode_movie.py and make_thumbnail.py has the identical failure mode. If single-frame ranges are meant to be unsupported now, consider validating FrameRange and emitting a clear message rather than letting the unpack throw.

@crowecawcaw
crowecawcaw merged commit 16fd81a into aws-deadline:mainline Jul 30, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-maintainers Waiting on the maintainers to review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants