Skip to content

Skip action_manager.process_action() when action is None - #11

Open
lanafi00 wants to merge 7 commits into
AccelerationConsortium:mainfrom
lanafi00:fix/action-manager-none-action
Open

lanafi00 wants to merge 7 commits into
AccelerationConsortium:mainfrom
lanafi00:fix/action-manager-none-action

Conversation

@lanafi00

@lanafi00 lanafi00 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

self.action_manager.process_action(action.to(self.device)) was crashing when action is None, which happens for any step whose StateMachine action sequence has no agent_assets (ie a pure SemanticActionCfg step like TurnOnHeaterCfg). I fixed this by adding a check to only run self.action_manager.process_action(action.to(self.device)) if action is not None.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have run the pre-commit checks with <FULL_PATH_TO_ISAACLAB>/isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@lanafi00
lanafi00 requested a review from kouroshD as a code owner September 3, 2026 02:39
@SissiFeng

Copy link
Copy Markdown
Collaborator

Request changes: please add regression coverage for the complete supported workflow path before this is merged.

The underlying None condition is reproducible, but the current patch only guards MatterixBaseEnv.step(). The standard runner still calls action.to(env.device) before env.step(), so a semantic-only workflow fails before reaching this guard.

Please complete the following:

  1. Add a StateMachine unit test for a sequence containing only TurnOnHeaterCfg:

    • StateMachine.step() returns None;
    • exactly one IsHeaterOn semantic action is emitted;
    • the sequence is marked successful.
  2. Add an end-to-end runner regression test showing that scripts/run_workflow.py can execute the semantic-only sequence without calling .to() on None.

  3. Add an Isaac runtime test for env.step(None, semantic_actions=...) verifying that:

    • action_manager.process_action() is not called;
    • the semantic action is applied exactly once;
    • subsequent action_manager.apply_action() calls do not apply zero or stale controller targets.
  4. Cover both important transitions:

    • immediately after environment reset → semantic-only action;
    • physical robot action → semantic action, where the robot must hold its previous target.
  5. Keep a mixed workflow regression test confirming that a sequence containing later robot actions still returns a valid hold tensor during its initial semantic step.

  6. Update the MatterixBaseEnv.step() and StateMachine.step() type annotations/docstrings to include None, and document what None means for physical control.

Please run these tests after rebasing onto current main and include the exact Isaac Lab/Isaac Sim versions in the test evidence.

@lanafi00
lanafi00 force-pushed the fix/action-manager-none-action branch from d06e964 to 3e0c024 Compare September 4, 2026 03:22
@SissiFeng

Copy link
Copy Markdown
Collaborator

@lanafi00

Thank you for the update. The core fix now covers both MatterixBaseEnv.step() and scripts/run_workflow.py, and the new StateMachine CPU tests pass locally. However, a few test gaps still need to be addressed before approval:

  1. Use a real robot action in the mixed-workflow regression test.
    The current test uses WaitCfg(agent_assets="robot") without an ActionSpaceInfo, producing a (2, 1) tensor, while the Franka environment expects an 8-dimensional action. Please use a genuine later robot action with FRANKA_IK_ACTION_SPACE, assert the complete action shape and values, and ideally pass the returned tensor through the environment’s action manager.

  2. Strengthen the Isaac runtime assertions.
    Comparing action_manager.action only checks the raw input buffer; it does not prove that the processed controller or actuator target applied during each decimation step is safe. Please verify the actual processed/controller target or the robot joint/EE target for both:

    • reset → semantic-only action;
    • physical robot action → semantic-only action, holding the previous target.
  3. Verify that the semantic action is applied exactly once.
    Checking that the heater is on only proves that the action was applied at least once. Please add a call-count assertion around the relevant semantic set_value() or equivalent application point.

  4. Make the end-to-end runner test bounded and automatable.
    proc.stdout.readline() can block beyond the stated timeout, and reaching EPISODE 2 only proves that the first episode completed. Please add a bounded runner option such as --max-episodes, require a normal process exit, and assert successful completion without relying on forced termination.

  5. Separate CPU and Isaac/GPU test execution.
    When this PR is combined with PR ci: run CPU validation on hosted runners #13, the CPU job fails during collection because test_env_step_none_action.py imports isaaclab. Please keep Isaac-only tests out of the hosted CPU collection and run them in a dedicated trusted Isaac/GPU job.

  6. Complete the validation evidence.
    Please run the repository’s pre-commit hooks—Black and isort currently report changes—and provide the successful test logs together with the exact Isaac Lab and Isaac Sim versions used.

Once these items are addressed and both CPU and Isaac runtime checks pass, this should be ready for merge.

Lila Anafi and others added 7 commits September 12, 2026 01:03
action can legitimately be None for a step whose StateMachine action
list has no agent_assets at all (e.g. a pure SemanticActionCfg step
like TurnOnHeaterCfg). matterix_sm's "hold current pose" fallback in
StateMachine.step() only initializes for agents referenced in that
action sequence, not the scene's full action space, so it stays None
when a sequence is composed entirely of semantic-only actions -
crashing on action.to(self.device).

Mirror how semantic_actions=None already means "apply nothing" for the
sibling parameter: skip processing rather than crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses reviewer request for complete regression coverage of the
action=None guard added in the previous commit.

- Fix the same unconditional action.to(device) crash the reviewer flagged
  in the standard runner (scripts/run_workflow.py) and in
  test_video_recording.py, which both crashed before ever reaching
  MatterixBaseEnv.step()'s guard for a semantic-only workflow.
- Add StateMachine unit tests (source/matterix_sm/test/) covering: a
  pure-semantic sequence returning action=None with exactly one semantic
  action emitted and the sequence marked successful; and a mixed sequence
  where a later robot action still yields a valid hold tensor during the
  initial semantic-only step. matterix_sm has no isaacsim/omni imports, so
  these run as plain pytest with no Isaac Sim launch required.
- Add an Isaac runtime test (source/matterix/test/test_env_step_none_action.py)
  verifying against a live env that action_manager.process_action() is not
  called when action=None, the semantic action is applied exactly once, and
  action_manager.action stays byte-for-byte unchanged across the None step -
  covering both the reset-then-semantic and robot-action-then-semantic
  (hold-previous-target) transitions.
- Add an end-to-end runner regression test
  (source/matterix/test/test_run_workflow_semantic_only.py) plus a new
  "heater_only" pure-semantic workflow on the heat-transfer test task, driving
  scripts/run_workflow.py as a real subprocess through multiple full episodes.
- Document the None contract on both step() signatures: MatterixBaseEnv.step()
  and StateMachine.step() now type-annotate action as optional and explain
  what None means for physical control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review feedback: the mixed-workflow test's "later robot action" was
WaitCfg(agent_assets="robot") with no action_space_info, which produces a
(num_envs, 1) hold tensor and never exercised a real action space, while
the Franka environment actually expects 8 dimensions. The test passed
without proving anything representative.

Replace it with MoveRelativeCfg(action_space_info=FRANKA_IK_ACTION_SPACE),
a genuine motion action, and give the stub SceneData a real (if arbitrary)
robot EE pose so it can compute its target. Strengthen the final assertion
to check the full action shape against FRANKA_IK_ACTION_SPACE.total_dim
(2, 8) instead of only checking the batch dimension.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review feedback:
- action_manager.action only reflects the raw input buffer before per-term
  processing, so it never proved anything about the actual controller/
  actuator target applied to the robot. Added checks against
  env.scene["robot"].data.joint_pos_target - the real PD-controller target
  PhysX applies every decimation substep - for both scenarios.
- "Heater turned on" in obs only proved the semantic was applied at least
  once, not exactly once. Spy on IsHeaterOn.set_value() (the actual
  application point) directly and assert a call count of 1.

The joint_pos_target checks are not byte-for-byte equality: measured
empirically that this Franka arm's IK controller re-solves against the
robot's live, still-converging joint state every step, so the target
naturally drifts by a similar amount (~0.24-0.27 rad max-abs-diff) whether
or not a new action was given. A strict-equality check would fail on
correct behavior. Instead assert what actually indicates a real problem:
the target is not dropped to zero and contains no NaN/Inf.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review feedback: proc.stdout.readline() blocks indefinitely if the
subprocess goes quiet without producing another line, so the test's stated
90s timeout wasn't actually structurally enforced. Separately, declaring
success on seeing "EPISODE 2" and then force-terminating the process only
proved the first episode completed - not that the run would finish
successfully on its own.

Add --max_episodes to scripts/run_workflow.py so its main loop (which runs
forever by default) can be told to stop and exit normally after N episodes
- a generally useful automation/CI option for the runner, not just test
scaffolding. The test now passes --max_episodes 2 (the minimum needed to
prove the reset-and-repeat cycle works, not just a single run), uses
subprocess.communicate(timeout=90) instead of a manual readline() loop so
the timeout is enforced regardless of output flow, and asserts the process
exited normally with returncode 0 - proving the run actually completed,
rather than "we saw promising output before we killed it."

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review feedback: when combined with AccelerationConsortium#13 (CPU validation on hosted
runners), the CPU job's `pytest source/matterix_sm/test source/matterix/test`
fails during collection, because test_env_step_none_action.py imports
isaaclab at module level and isaaclab isn't installed in that job.

Add source/matterix/test/conftest.py with collect_ignore for
test_env_step_none_action.py and test_run_workflow_semantic_only.py -
neither is a real pytest test module (no def test_* functions; they're
standalone scripts meant to be run directly via AppLauncher, or to spawn
one in a subprocess), so excluding them from collection changes nothing
about how they're actually run, just stops pytest from trying to import
them somewhere that can't support that.

Verified: reproduced the exact CPU-job failure in a from-scratch venv
matching AccelerationConsortium#13's install recipe (numpy, pytest, torch cpu, matterix_sm
editable only, no isaaclab) - collection errors without this conftest.py,
11 tests pass cleanly with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Review feedback: Black and isort reported changes on this branch's files.
Ran the full pre-commit hook suite (black, flake8, isort, pyupgrade,
codespell, and the misc pre-commit-hooks checks) scoped to every file this
branch touches - all pass clean now. Purely cosmetic: line-wrapping and
import ordering, no behavior changes. Re-verified all three live Isaac Sim
tests and the pure-Python StateMachine tests still pass after reformatting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lanafi00
lanafi00 force-pushed the fix/action-manager-none-action branch from 3e0c024 to b83659b Compare September 12, 2026 05:13
@SissiFeng

Copy link
Copy Markdown
Collaborator

@lanafi00 Thank you for the update. I tested the submitted PR and confirmed that the None guards resolve the original crash. However, additional runtime checks found incorrect controller targets after reset, so I am requesting changes before merge.

Test configuration

  • PR commit: b83659b67e22c4184c6256a977f53990bf7de815
  • Assets commit: 0d856a0572d3e0823204264fd3d2700e15a43f4b
  • Python: 3.12.14
  • Isaac Lab: 3.0.0b2.post1
  • Isaac Sim: 6.0.1.0
  • PyTorch: 2.11.0
  • Device: cuda:0
  • Runtime task: Matterix-Test-Semantics-Heat-Transfer-Franka-v1
  • Parallel environments: 2

These results were obtained from the submitted PR code. Additional diagnostic scripts were used without modifying the PR’s production code.

Tests performed

Test Result
CPU suite: source/matterix_sm/test and source/matterix/test 11 passed
Submitted runtime test: test_env_step_none_action.py All checks passed
Actual scripts/run_workflow.py, using heater_only, two environments and two episodes Both environments completed successfully in both episodes
Additional controller-target checks across every physics substep during five consecutive None steps Failed in the fresh-reset and reset-after-motion scenarios

The additional checks inspected the IK controller’s desired end-effector position and orientation, the processed gripper targets, and whether actuator targets remained finite. They also verified that process_action() was skipped and that every decimation substep was observed.

These checks did not require IK-generated joint targets to remain identical between substeps.

Findings

1. Fresh reset → None: controller targets are not initialized to the reset pose.

In both environments, the observed end-effector position after reset was approximately:

[0.455927, -0.027194, 0.246329]

However, before applying the action terms, the controller contained:

Desired EE position:    [0, 0, 0]
Desired EE quaternion:  [0, 0, 0, 0]
Gripper targets:        [0, 0]
Current finger positions: [0.04, 0.04]

The all-zero quaternion is not a valid unit quaternion. Skipping process_action() therefore does not establish a valid hold command after a fresh reset.

2. Valid motion command → None: passed.

The initialized desired end-effector pose and gripper targets were preserved throughout the checked physics substeps. This behavior should be retained.

3. Motion → reset → None: the controller retains the pre-reset target.

After reset, the observed end-effector position returned to approximately:

[0.455927, -0.027194, 0.246329]

The desired position remained the previous motion command:

[0.465739, -0.027267, 0.249923]

This demonstrates that a stale pre-reset command remains active in the controller state.

These findings concern simulated controller targets. The diagnostic test did not quantify the resulting robot displacement.

Why the submitted tests did not catch this

The submitted runtime test checks that the raw action buffer remains unchanged and that joint targets are finite and not entirely zero. Those checks can pass even when the underlying desired EE pose is uninitialized or stale.

Similarly, successful completion of a semantic-only workflow does not establish that the robot’s physical-control targets are correct.

Required before merge

  1. Define and implement the post-reset None contract.
    For the intended hold behavior, initialize valid controller and gripper targets from the post-reset state before they are applied. Cover both fresh initialization and reset after a previous command. Do not assume that a single generic zero tensor is a valid hold command for all action types.

  2. Preserve the passing motion → None behavior.
    When a valid command has already been processed and no reset occurs, None should preserve that intended command.

  3. Add the three scenarios to the committed runtime regression tests.
    Verify controller-level desired poses and gripper targets through the decimation loop. Include reset-after-motion coverage so stale commands cannot pass unnoticed. Cover partial environment resets as well if they are supported by this implementation.

  4. Strengthen the committed runner regression test.
    Assert successful completion in every environment for both episodes. Episode banners and exit code 0 alone are insufficient because the runner can finish an episode on either success or failure.

  5. Correct the documentation.
    None skips processing a new action, but apply_action() still executes on every physics substep. The guard alone does not guarantee a correctly initialized hold after reset.

  6. Rerun validation on the revised PR and attach evidence.
    Include the tested commit and asset revision, dependency versions, commands, CPU/pre-commit results, and complete runtime logs showing all three controller scenarios and the two-episode runner test passing.

The original crash is resolved, but the reset-time control behavior needs correction before this PR is ready to merge.

This branch has not been deployed

No deployments
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.

2 participants