From 8852275d514fee2f90e157ec6c0a6b30be1db090 Mon Sep 17 00:00:00 2001 From: devdeepr Date: Thu, 7 May 2026 20:40:44 +0000 Subject: [PATCH 1/2] fix(dex-hand): release temp YAML config after optimizer build DexHandRetargeter._update_yaml wrote a tempfile per construction (so the URDF path could be patched without mutating the original config) and never removed it. Long-running sessions or test suites that built many retargeters left a stream of /tmp/dex_retarget_*.yml files behind. Track the temp path on the instance and unlink it once RetargetingConfig.load_from_file(...).build() has consumed the YAML. __del__ acts as a safety net for cases where the optimizer build raises between writing the temp file and the explicit cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/retargeters/dex_hand_retargeter.py | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/retargeters/dex_hand_retargeter.py b/src/retargeters/dex_hand_retargeter.py index b056de146..6b054e071 100644 --- a/src/retargeters/dex_hand_retargeter.py +++ b/src/retargeters/dex_hand_retargeter.py @@ -135,6 +135,9 @@ def __init__(self, config: DexHandRetargeterConfig, name: str) -> None: config.hand_retargeting_config ).build() + # The optimizer has parsed the YAML; the temp file is no longer needed. + self._cleanup_temp_config() + # Cache joint names from optimizer self._dof_names = self._dex_hand.optimizer.robot.dof_joint_names @@ -177,6 +180,14 @@ def __init__(self, config: DexHandRetargeterConfig, name: str) -> None: HandJointIndex.LITTLE_TIP, ] + def __del__(self) -> None: + # Safety net for callers that construct a retargeter and abandon it + # before the optimizer build completes (e.g. a config-load exception). + try: + self._cleanup_temp_config() + except Exception: + pass + def input_spec(self) -> RetargeterIOType: """Define input collections for hand tracking (Optional).""" if self._hand_side == "left": @@ -302,6 +313,24 @@ def _prepare_configs(self) -> None: if temp_config: self._config.hand_retargeting_config = temp_config + # Track for cleanup once the optimizer has consumed the file. + self._temp_config_path: Optional[str] = temp_config + else: + self._temp_config_path = None + + def _cleanup_temp_config(self) -> None: + """Remove the temp YAML written by ``_update_yaml`` once the optimizer + has been built from it. Safe to call repeatedly.""" + path = getattr(self, "_temp_config_path", None) + if not path: + return + try: + os.unlink(path) + except FileNotFoundError: + pass + except OSError as e: + logger.warning(f"Failed to remove temp dex_retargeting config {path}: {e}") + self._temp_config_path = None def _update_yaml(self, yaml_path: str, urdf_path: str) -> Optional[str]: """ From 2fb544d1342852650814c3aba985a838014f1dfb Mon Sep 17 00:00:00 2001 From: devdeepr Date: Thu, 7 May 2026 21:10:33 +0000 Subject: [PATCH 2/2] review(dex-hand): stop mutating caller's config to a deleted path CodeRabbit and Copilot flagged that _prepare_configs was overwriting self._config.hand_retargeting_config with the temp YAML path; once _cleanup_temp_config unlinks that file the config object now points at a deleted path. Callers that reuse the same DexHandRetargeterConfig would then fail to construct another retargeter. Resolve the load path internally (self._retargeting_config_path) and leave the caller's config untouched. The temp file is still tracked separately for cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/retargeters/dex_hand_retargeter.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/retargeters/dex_hand_retargeter.py b/src/retargeters/dex_hand_retargeter.py index 6b054e071..c7fc0b831 100644 --- a/src/retargeters/dex_hand_retargeter.py +++ b/src/retargeters/dex_hand_retargeter.py @@ -130,9 +130,10 @@ def __init__(self, config: DexHandRetargeterConfig, name: str) -> None: # Setup paths and configs self._prepare_configs() - # Initialize dex retargeting optimizer + # Initialize dex retargeting optimizer (use the internal load path so + # the user's config object is not mutated to point at our temp file). self._dex_hand = RetargetingConfig.load_from_file( - config.hand_retargeting_config + self._retargeting_config_path ).build() # The optimizer has parsed the YAML; the temp file is no longer needed. @@ -311,12 +312,14 @@ def _prepare_configs(self) -> None: self._config.hand_retargeting_config, local_urdf ) - if temp_config: - self._config.hand_retargeting_config = temp_config - # Track for cleanup once the optimizer has consumed the file. - self._temp_config_path: Optional[str] = temp_config - else: - self._temp_config_path = None + # Resolve the load path internally rather than mutating the user's + # ``DexHandRetargeterConfig``: that object may be reused or inspected + # by the caller, and overwriting it with a path we are about to + # delete in ``_cleanup_temp_config`` would leave it dangling. + self._retargeting_config_path: str = ( + temp_config or self._config.hand_retargeting_config + ) + self._temp_config_path: Optional[str] = temp_config def _cleanup_temp_config(self) -> None: """Remove the temp YAML written by ``_update_yaml`` once the optimizer