Skip to content

Fix warnings capture, hook-failure tracing, and a class-shadow false positive - #101

Open
gauravprasadgp wants to merge 4 commits into
NVIDIA-NeMo:mainfrom
gauravprasadgp:contrib/local-fixes
Open

Fix warnings capture, hook-failure tracing, and a class-shadow false positive#101
gauravprasadgp wants to merge 4 commits into
NVIDIA-NeMo:mainfrom
gauravprasadgp:contrib/local-fixes

Conversation

@gauravprasadgp

Copy link
Copy Markdown

What's in here

Warnings capturewarnings.warn() bypassed the ContextVarStream stdout/stderr capture since it goes through Python's warning machinery, not sys.stderr.write() directly. Installs a showwarning hook that writes into sys.stderr, re-installed before every execution since pytest resets warnings.showwarning per test.

Hook-failure tracing -> after_agent_call only fired when hook_context is not None, which can't tell "middleware short-circuited" apart from "before_agent_call raised and call_before_hook swallowed it." The second case silently dropped the trace. Now tracks whether the before-hook actually ran, and OpenInferenceHooks.after_agent_call emits a
minimal ERROR span when it gets no context instead of skipping the call entirely.

Class-assignment false positive -> ClassAssignmentValidator blocks
ClassName.attr = value to stop generated code corrupting shared class state, but matched on name only. ClassName = {} followed by ClassName.attr = value (a plain local dict) got flagged too — a known false positive the tests had marked "acceptable" instead of fixed. Now tracks the first top-level reassignment of a class name and treats writes after that line as safe. Only top-level statements count, on purpose: a reassignment inside if/for/try could be on a branch that never runs, and honoring it there would let generated code fake a shadow to slip a real corruption past the check.

eval_pipeline docs -> it's already a uv workspace member in the dev group, so uv sync --group dev installs it editable. The README still said pip install -e.

Testing

  • pytest tests/runtime — 1055 passed
  • pytest tests/integration/test_hook_failure_traces.py — 4 passed
  • pytest tests/runtime/test_code_validator.py — 228 passed
  • pytest (full suite, minus test_mcp which fails on main too from a missing optional
    dependency) — 5222 passed
  • ruff check and pyright clean on touched files

warnings.warn() writes through Python's warning machinery rather than
sys.stderr.write() directly, so it bypassed the ContextVarStream wrapper
used to capture stdout/stderr from generated code. Under pytest this was
extra confusing because the warnings-capture plugin replaces
warnings.showwarning for the duration of every test, which would swallow
the warning before it ever reached our wrapper.

Install a showwarning hook that writes formatted warnings to sys.stderr
(and therefore into the active capture buffer, if any), re-installed
before every code execution so it isn't left stale by whatever else
touched warnings.showwarning in the meantime.

Signed-off-by: gauravprasad <prasadgaurav559@gmail.com>
call_before_hook swallows exceptions from a hooks implementation and
returns None, which was indistinguishable from before_agent_call simply
never running (e.g. when agent_call middleware short-circuits). Both
wrapper paths gated after_agent_call on "hook_context is not None", so a
hook that raised silently lost after_agent_call too - the call happened,
but nothing about it was ever traced.

Track whether before_agent_call was actually attempted separately from
whether it returned usable context, and fire after_agent_call in either
case. OpenInferenceHooks.after_agent_call now falls back to a minimal
ERROR span when it has no context to complete, so a failed hook still
leaves a trace record instead of vanishing entirely.

Signed-off-by: gauravprasad <prasadgaurav559@gmail.com>
eval_pipeline is already a uv workspace member and ships in the "dev"
dependency group, so uv sync --group dev installs it editable along with
everything else. The README still told contributors to pip install -e it
separately, which contradicts the repo's uv-only convention and is just
unnecessary. Point external consumers at the git+ install instead.

Signed-off-by: gauravprasad <prasadgaurav559@gmail.com>
ClassAssignmentValidator blocks ClassName.attr = value to keep generated
code from corrupting shared class state, but it matched purely on
variable name. Once code did ClassName = {} (or any other reassignment),
ClassName was a local variable for the rest of the block, yet the
validator still rejected ClassName.attr = value as if it were touching
the class - a known false positive the tests documented as "acceptable"
rather than fixed.

Track the line of the first top-level ClassName = <expr> reassignment
per known class name and treat attribute writes after that line as safe.
Restricted to top-level statements only: a reassignment nested inside
if/for/try could be on a branch that never executes, and honoring it
there would let generated code fake a shadow to bypass the check on a
real corruption elsewhere. Added a test covering exactly that bypass
attempt to make sure it stays rejected.

Signed-off-by: gauravprasad <prasadgaurav559@gmail.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.

2 participants