diff --git a/agent/skill_commands.py b/agent/skill_commands.py index 18414199dc..1f000eefed 100644 --- a/agent/skill_commands.py +++ b/agent/skill_commands.py @@ -168,7 +168,7 @@ def _build_skill_message( subdir_path = skill_dir / subdir if subdir_path.exists(): for f in sorted(subdir_path.rglob("*")): - if f.is_file(): + if f.is_file() and not f.is_symlink(): rel = str(f.relative_to(skill_dir)) supporting.append(rel) diff --git a/cron/scheduler.py b/cron/scheduler.py index cdd6877f92..0e04fb047b 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -442,6 +442,14 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: stdout = (result.stdout or "").strip() stderr = (result.stderr or "").strip() + # Redact secrets from both stdout and stderr before any return path. + try: + from agent.redact import redact_sensitive_text + stdout = redact_sensitive_text(stdout) + stderr = redact_sensitive_text(stderr) + except Exception: + pass + if result.returncode != 0: parts = [f"Script exited with code {result.returncode}"] if stderr: @@ -450,13 +458,6 @@ def _run_job_script(script_path: str) -> tuple[bool, str]: parts.append(f"stdout:\n{stdout}") return False, "\n".join(parts) - # Redact any secrets that may appear in script output before - # they are injected into the LLM prompt context. - try: - from agent.redact import redact_sensitive_text - stdout = redact_sensitive_text(stdout) - except Exception: - pass return True, stdout except subprocess.TimeoutExpired: diff --git a/tools/process_registry.py b/tools/process_registry.py index 9f57d3eaeb..fb656d0f3a 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -396,15 +396,15 @@ class ProcessRegistry: session.output_buffer = session.output_buffer[-session.max_output_chars:] except Exception as e: logger.debug("Process stdout reader ended: %s", e) - - # Process exited - try: - session.process.wait(timeout=5) - except Exception as e: - logger.debug("Process wait timed out or failed: %s", e) - session.exited = True - session.exit_code = session.process.returncode - self._move_to_finished(session) + finally: + # Always reap the child to prevent zombie processes. + try: + session.process.wait(timeout=5) + except Exception as e: + logger.debug("Process wait timed out or failed: %s", e) + session.exited = True + session.exit_code = session.process.returncode + self._move_to_finished(session) def _env_poller_loop( self, session: ProcessSession, env: Any, log_path: str, pid_path: str, exit_path: str