Skip to content

docs(setup): explain local lockfile needed by tox - #7318

Closed
iarjunganesh wants to merge 1 commit into
google:mainfrom
iarjunganesh:docs/adk-setup-lockfile
Closed

iarjunganesh wants to merge 1 commit into
google:mainfrom
iarjunganesh:docs/adk-setup-lockfile

Conversation

@iarjunganesh

Copy link
Copy Markdown
Contributor

Link to Issue or Description of Change

Problem: The setup skill says dependencies are pinned in uv.lock, but this
repository ignores that file. On a fresh checkout, the tox lock runner fails
with Unable to find lockfile at uv.lock until uv sync has run.

Solution: Explain that uv sync creates a local lockfile and must run before
tox.

Testing Plan

  • Confirmed uv.lock is untracked and ignored by the repository.
  • Reproduced the tox lockfile error before uv sync; after syncing, tox reached
    the pytest suite.
  • pre-commit run --files .agents/skills/adk-setup/SKILL.md passed.

Checklist

  • Read CONTRIBUTING.md and reviewed the change.
  • Checked the documented sequence against the current tox configuration.

uv.lock is ignored in this repository, so contributors need to run uv sync before the tox lock runner. State that sequence explicitly in the setup skill.

Signed-off-by: Arjun Ganesh <iarjunganesh@gmail.com>
copybara-service Bot pushed a commit that referenced this pull request Sep 29, 2026
@adk-bot

adk-bot commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

Thank you @iarjunganesh for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit 96319fc.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Sep 29, 2026
@adk-bot adk-bot closed this Sep 29, 2026
caohy1988 added a commit to caohy1988/adk-python that referenced this pull request Sep 30, 2026
…ytics error_message (#14)

* fix(plugins): record content_formatter failure class in BigQuery rows

When BigQueryLoggerConfig.content_formatter raised, or returned a type the
parser cannot store, BigQueryAgentAnalyticsPlugin wrote the
[FORMATTER_FAILED] sentinel, logged a constant warning, and left the
row's error_message NULL, so the developer had no signal about why.

Set error_message to a fixed-shape description that names only a class,
for example "content_formatter raised ImportError" or "content_formatter
returned unsupported type tuple", and never the exception message, args,
or traceback, which can embed the content the formatter was protecting.
Because type(name, ...) can mint a class named after that content, a name
is used only when code chose it: a static type compiled into C, or a
class bound under that name in its imported module. Any other class is
described by its nearest such ancestor, for example "content_formatter
raised <subclass of ValueError>". An event that already carries an
error_message, such as a TOOL_ERROR, keeps it first, followed by "; " and
the formatter failure.

Add BigQueryLoggerConfig.debug_content_formatter_errors (default False),
which attaches the traceback to the local formatter-failure warning for
debugging. The traceback is never written to BigQuery, and the docstring
warns that the process's log handlers can forward it, and the content it
embeds, elsewhere.

The fail-closed contract is unchanged: the sentinel content, the constant
log text by default, and the formatter_failed drop counter.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: stamp Redis sessions with the event timestamp

Merge google#7293

Fixes google#7292

PiperOrigin-RevId: 990023711

* fix(plugins): stop formatter diagnostics leaking names or dropping rows

Review of the previous commit found three ways the new content_formatter
diagnostics could still leak payload text or drop a row. This closes them.

Trusted labels only. A class created at runtime can be named after the
content a formatter was protecting and then bound into its module, so "the
class is bound under its name in its module" proved nothing, and such a
name reached error_message. error_message now names a class only by a label
that no runtime data can have chosen: the compiled name of a static C type,
or a fixed string for an allowlisted class matched by identity
(LlmRequest, types.Content, types.Part, pydantic BaseModel, and
google.api_core GoogleAPICallError). Every other class, including every
Python-defined library exception and every class the developer defines, is
described by its nearest trusted ancestor, for example "content_formatter
raised <subclass of ValueError>". Trade-off: fewer exact names, for example
json.JSONDecodeError now reads "<subclass of ValueError>" and a
google.api_core NotFound "<subclass of GoogleAPICallError>";
debug_content_formatter_errors shows the exact class locally.

No class code runs while diagnosing. Flags, name, and MRO are read through
type's own descriptors, and allowlist matching compares identity, so a
metaclass __getattribute__, __eq__, or __hash__ can no longer run inside
the fail-closed boundary. Such a hook could raise asyncio.CancelledError,
which escaped the boundary's `except Exception` and dropped the row.
Classification now cannot raise at all, so it needs no BaseException
handler, and a genuine KeyboardInterrupt or SystemExit delivered by a
signal still propagates. The exact-class check on formatter results also
compares identity now, for the same reason.

Best-effort debug traceback. With debug_content_formatter_errors, the
traceback is rendered to text once, inside the plugin, and appended to the
warning; handlers never receive the live exception, whose own code could
otherwise fail inside a stock StreamHandler and drop the row. A rendering
failure falls back to a constant placeholder. KeyboardInterrupt and
SystemExit still propagate, because a signal can deliver them at any
bytecode; any other BaseException raised while rendering, CancelledError
included, can only come from the exception being rendered, since rendering
never awaits. The warning is emitted after the except block, so a failing
handler's handleError cannot reach the formatter's exception through its
exception chain.

Tests pin each guard: registered payload-named classes and their
subclasses, a renamed allowlisted class, metaclass hooks raising
CancelledError, SystemExit, or RuntimeError on both failure paths,
unrenderable tracebacks under a stock StreamHandler, interrupt propagation,
and a failing log handler. Removing any guard fails at least one test.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(plugins): keep formatter diagnosis from ever dropping the row

Review of the previous commit showed three more ways that diagnosing a
content_formatter failure could escape and drop the row, even after the
hook-by-hook fixes:

- a rendering hook raising SystemExit or KeyboardInterrupt, which the
  previous policy re-raised as possibly genuine;
- type's own descriptors raising TypeError once a meta-metaclass drops
  `type` from the failed class's metaclass MRO, which disproved
  "classification cannot raise";
- a log handler or filter raising while the warning is emitted, which
  also misattributed an unsupported result as "raised RuntimeError".

Instead of patching one hook at a time, all of diagnosis (naming the
failed class, rendering the debug traceback, and emitting the warning)
now runs in _diagnose_formatter_failure, behind one boundary that
contains whatever is raised, of any type, and falls back to a constant
note. It runs only after the fail-closed state is settled: the
formatter's try/except now only records the failure and writes the
sentinel, and the failure is counted before diagnosis starts. Nothing
diagnosis does can reach the row, the sentinel, the counter, or the
callback's caller.

BaseException is contained deliberately. An interrupt raised by a hook,
handler, or filter cannot be told apart from one a signal handler
delivered, and letting it through would let the content under redaction
abort the agent run and lose the row. Diagnosis never awaits, so a real
asyncio cancellation is never swallowed; a signal that lands inside the
short window is absorbed, and the next is delivered normally. Interrupts
raised by the formatter call itself still propagate, as before.

Two inner guards remain where they keep the warning: labeling falls
back to "<unknown class>" when type's descriptors raise, and rendering
falls back to a placeholder. Diagnosis still runs after the except
block, so a failing handler's handleError cannot print the formatter's
exception.

A result whose __class__ merely claims to be str is now reported as an
unsupported result instead of "raised TypeError".

Tests: a property test injects RuntimeError, CancelledError,
KeyboardInterrupt, SystemExit, and another BaseException at each
diagnostic step (labeling, rendering, a handler, a filter) on both paths
and requires the sentinel row and its count. Also added: the
meta-metaclass regression on both paths, rendering hooks raising
interrupts, a stderr check that a failing handler never prints the
formatter's exception, and a guard that interrupts from the formatter
call still propagate. Removing the boundary or any guard fails a test.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(plugins): close remaining formatter paths that leak or drop rows

Review of the previous commit found three more paths, each also present
on main, by which a content_formatter failure could still leak payload
text or lose the row:

- The failure warning can be logged while the caller is handling an
  exception, as ADK is when it runs error callbacks. A log handler that
  fails prints the exception being handled through handleError, so a
  closed stream could print the caller's exception, or the formatter's
  when it re-raised that one, to stderr.
- The result check used isinstance(formatted, (dict, list)), which falls
  back to the object's own __class__. A returned object could raise
  CancelledError, SystemExit, or KeyboardInterrupt there and lose the
  row, or raise an ordinary exception that was then reported as the
  formatter having raised.
- A rejected coroutine was released unstarted, so Python warned
  "coroutine '<name>' was never awaited", and the formatter can set that
  name from the content.

Everything after the formatter call now runs in
_settle_formatter_outcome, behind the one boundary that already
contained diagnosis: judging the result, closing a rejected coroutine or
generator, naming the class, rendering the debug traceback, and logging.
The result is judged only by its real type, through issubclass(type(x),
...) and identity, which runs none of its code. A str, dict, or list
subclass is still accepted; an object whose __class__ merely claims to
be one is now a counted formatter failure rather than an
[UNSUPPORTED_OBJECT] row. A rejected coroutine or generator is closed
before release, which for an unstarted one runs no code and emits no
warning. The warning is emitted while a constant stand-in exception,
raised from None, is being handled, so a failing handler prints only
that. The formatter call itself still lets KeyboardInterrupt,
SystemExit, and CancelledError propagate.

Docs: the content_formatter docstring now says "raises an Exception",
explains judging by real type, and notes that interrupts from the call
propagate. The boundary's docstring states plainly that a genuine
signal delivered while it runs, including while a handler blocks, is
absorbed. The debug flag notes that a failing handler echoes the
rendered traceback to stderr.

Tests: a caller handling a payload exception with a closed handler
(re-raised and distinct); results whose __class__ property or
__getattribute__ raises each interrupt type or claims to be a dict; an
async formatter and a payload-named coroutine, with warnings recorded;
and two more steps in the injection property test, judging the result
and closing a rejected generator. Removing any guard fails a test.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(plugins): honor genuine interrupts and isolate all plugin logging

Review of the previous commit found two remaining gaps, and two guards
that no test pinned:

- A KeyboardInterrupt or SystemExit delivered while a content_formatter
  failure was being described was absorbed. A real SIGINT, or a SIGTERM
  whose handler calls sys.exit while a log handler blocks on I/O, left
  the row written but the process running, unaware of the signal.
- Only the formatter-failure warning ran while a stand-in exception was
  handled. Any other plugin warning, such as the parser's, could still
  have a failing handler print the exception the caller is handling, as
  ADK is when it runs error callbacks.

Interrupts are now sorted by the code that raised them, because Python
cannot tell a signal from a direct raise. The failed class's and the
rejected result's own code runs only while the debug traceback is
rendered and while a rejected generator is closed. Each of those steps
has its own guard, and whatever it raises, interrupts included, is
contained, so the content under redaction cannot end the agent run.
Everywhere else in the boundary only plugin code and the application's
log filters and handlers run, so a KeyboardInterrupt or SystemExit there
came from a signal or from the application. _settle_formatter_outcome
returns it as a new exception without text (a SystemExit keeps an int
exit code), and _log_event raises it once the row has been handed to
the writer. CancelledError stays contained: nothing in the boundary
awaits, so it cannot be a real cancellation. The trade-off is that a
signal landing while payload-controlled code runs is still absorbed.

The module logger's handle() now runs every record's filters and
handlers while a constant stand-in exception is handled, with its
__context__ cleared. So no plugin log call can print the caller's
exception through handleError, or through a handler that walks
__context__ and ignores __suppress_context__. Records are unchanged:
Logger._log resolves exc_info and the calling function before handle()
runs. This replaces the stand-in around the formatter warning alone.

The boundary's docstring now bounds its never-drop claim to failed or
rejected results. An admitted str, dict, or list subclass goes on to
the parser, whose boundary catches Exception only.

Tests:
- the injection property test expects an interrupt from any step but
  closing to be raised after the row, as a new exception, and checks
  that a rejected generator's cleanup never reaches the unraisable hook;
- a real SIGINT and SIGTERM delivered while the warning is emitted;
- a parse-failure warning with a closed handler, and with a handler
  that walks __context__, while the caller handles a payload exception;
- the plugin's error logs keep their own exc_info;
- supported results (None, a str subclass whose hooks raise, dict and
  list subclasses, and exact Content, Part, and LlmRequest) pass through
  unchanged.

Removing any new guard fails a test.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(plugins): note that closing a rejected coroutine is contained too

The boundary's docstring said the rejected result's own code runs only
while the debug traceback is rendered and while a rejected generator is
closed. The same guard also covers closing a rejected coroutine, which
runs the coroutine's own cleanup if it was started. Wording only.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat: honor tool_thread_pool_config for sync tools outside live mode

A synchronous function tool blocks the event loop while it runs, and RunConfig.tool_thread_pool_config, the setting that moves tools off the loop, only applied in live mode. With it set, run_async now runs a synchronous function tool's function on the tool thread pool when an LlmAgent calls the tool, while async tools and tools used directly as Workflow nodes stay on the event loop, and without it nothing changes.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 990376105

* fix: drop unpairable trailing FRs in rearrange

Merge google#6752

Fixes google#6751

PiperOrigin-RevId: 990390762

* fix: replay a parallel tool call that never ran when a sibling answered

When a resumed model turn had parallel calls and only some had a response, the resume decision treated one answer as covering the whole turn, so it continued to the model and the call that never ran was dropped. The decision now replays just the calls with no response, and only when the agent has written nothing since the call, so answered calls do not run a second time.

Close google#7108

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 990408003

* chore(scripts): detect added files through git only

The new-file check read added files and commit messages from several version
control systems. Contributors and CI use git through pre-commit, and jj's
default colocated repositories already take the git path, so the rest only
added code to maintain. Outside a git work tree the check now reports that it
could not determine the added files. A `//`-prefixed .py path with no file
behind it, the form Perforce depot paths take, is now refused.

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 990408500

* fix(tools): surface NodeTool failures to on_tool_error and return dict validation errors

NodeTool swallowed every exception raised while running its node and
returned a plain string, which the flow wrapped as {'result': '<string>'}.
As a result:
- on_tool_error callbacks (agent and plugin) never saw node failures, unlike
  every other BaseTool, and the original exception was lost behind the
  generic "Dynamic node <name> failed" wrapper.
- Input validation errors reached the model as {'result': ...} instead of the
  {'error': ...} shape FunctionTool uses for argument validation errors.

This change aligns NodeTool with FunctionTool:
- Input schema validation errors return {'error': ...} with the same wording
  as FunctionTool, so the model can correct its arguments and retry.
- When the node fails, the node's original exception is re-raised (chained
  from DynamicNodeFailError). The tool pipeline then runs on_tool_error
  callbacks with the real cause; if none handles it, the error propagates,
  as it does for FunctionTool. Node-level retry_config still applies first,
  inside run_node.

Behavior change: an agent using a node as a tool without an on_tool_error
callback now fails the run when the node fails, instead of passing an error
string to the model. This matches FunctionTool.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 990410996

* feat(mcp): add an opt-in modern-protocol connect path for MCP SDK 2.x

ADK always brought a session up with `initialize()`, which pins the connection
to the 2025 wire for its whole life even on SDK 2.x: the server issues an
`Mcp-Session-Id` to route later requests back to one instance, and `clientInfo`
is stated once rather than on each request.

Setting `ADK_ENABLE_MCP_MODERN_PROTOCOL=1` probes `server/discover` first and
falls back to the handshake on anything that is not a modern server, so a
2025-era server is unaffected. Off by default, and a no-op on SDK 1.x.

Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 990414661

* feat(workflow): propagate skip_summarization from node tools to tool response

Propagate `ctx.actions.skip_summarization` from dynamically executed child nodes
to the parent context in `run_node_internal`, and include `NodeTool` alongside
`AgentTool` when attaching displayable tool output to `function_response` events
with `skip_summarization=True`. This allows `@node` and `Workflow` tools to set
`ctx.actions.skip_summarization = True` inside the node so their terminal output
is emitted directly as the final user-visible text response without triggering a
follow-up LLM summarization turn, while keeping `NodeTool` internal.

Co-authored-by: Shangjie Chen <deanchen@google.com>
PiperOrigin-RevId: 990434765

* fix(eval): skip content-less events when mapping Vertex multi-turn turns

Invocations now keep the final event with its content removed so the efficiency metrics can read its token usage, and the Vertex multi-turn facade sent that event as an empty agent message in every turn. Skips intermediate events without content when mapping a turn.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 990461145

* fix: detect a dead MCP session whose transport sits behind a dispatcher

MCP SDK 2.x moved the transport off the client session and behind a dispatcher, so ADK's liveness check read attributes that no longer exist and a pooled session whose server had died looked healthy forever, failing every later call. A session with no streams of its own is now checked through its dispatcher's closed flag, which restores the existing reconnect path on both SDK majors.

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 990472674

* fix: only apply --avatar_config to live sessions requesting video

The server-wide avatar configuration added to `adk web` and
`adk api_server` was attached to every /run_live session, including the
default audio-only ones. Avatars are rendered as video, so only set
`RunConfig.avatar_config` when the client requests the VIDEO modality, and
say so in the `--avatar_config` help text.

Also adds a test for the unreadable avatar configuration file path.

Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 990498163

* fix: follow redirects when downloading skills in GcpSkillRegistry

Merge google#6824

PiperOrigin-RevId: 990507320

* test: cover api server auto create session

Merge google#6786

PiperOrigin-RevId: 990538126

* feat: propagate grounding metadata from MCP _meta

Merge google#7047

Fixes google#6081

PiperOrigin-RevId: 990543079

* docs: explain local lockfile needed by tox in adk-setup skill

Merge google#7318

PiperOrigin-RevId: 990578226

* feat: let BigQuery tools run where CMEK is required

Merge google#7232

Fixes google#3931

PiperOrigin-RevId: 990587308

* feat: add the model consult session context handover layer

When a small executor model escalates a hard step to a larger advisor
model, the advisor has to be told what has happened so far, or it answers
a question it does not understand. This adds the layer that turns a
session event log into contents the advisor can read:

- tool calls and tool results are flattened into plain text, so a model
  that was never given those tool declarations can still follow them;
- consecutive events from the same role are merged into one turn, which
  third-party advisor models require;
- the transcript is held to a character budget by keeping the head and
  the tail, marking the omitted middle, and trimming the newest turn when
  it does not fit on its own, so one long turn cannot silently multiply
  the cost of a consult;
- thoughts, media parts, per-part length and event count are each
  configurable, and the in-flight escalation call itself is skipped.

The layer is not wired into a tool yet; that follows in a later change.

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 990610529

* feat(tools): invoke advisor models without tools for model_consult

Invoke the advisor `BaseLlm` via `generate_content_async(req, stream=False)` with `config.tools = []` and `config.tool_config = None` so mid-task consultations return text guidance without entering a tool loop, while still emitting standard OpenTelemetry client duration and token usage metrics:
- `resolve_advisor_llm` and `resolve_thinking_level` for model and thinking-level normalization
- `call_advisor` with automatic fallback retry when `thinking_config` is rejected, `MAX_TOKENS` truncation and thought-exhaustion handling across `Gemini` and `LiteLlm`, and OpenTelemetry metric emission
- `AdvisorResult`, `AdvisorUsage`, and `AdvisorError`

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 990613834

* feat(tools): add ModelConsultTool with turn and session budgets

Add `ModelConsultTool`, an ADK tool that lets an executor `LlmAgent`
consult a stronger advisor model mid-generation without relinquishing
control of the conversation.

Key capabilities:
- Per-turn (`max_uses`) and session-wide (`session_max_uses`) consult
  budgets stored in session state (`temp:` and persistent state keys)
  with `has_remaining_budget` helper and per-session concurrency/delta
  coordination for parallel tool calls.
- Automatic context handover via `build_advisor_contents` (with both
  `'events'` and `'transcript'` handover modes on
  `ModelConsultContextConfig`).
- Forwards the executor's `static_instruction`, state-interpolated
  `canonical_instruction`, and non-self tool inventory (`canonical_tools`)
  to the advisor system instruction.
- Graceful degradation on budget exhaustion (`status='limit_reached'`),
  missing question (`status='invalid_request'`), and advisor runtime or
  timeout errors (`status='error'`).
- Top-level lazy export of `ModelConsultTool` from `google.adk.tools`.

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 990614577

* docs(tools): add ModelConsultTool developer guide and sample agent

Adds the developer guide and runnable order-support refund policy sample for `ModelConsultTool`:
- Adds the `ModelConsultTool` unit guide under `docs/guides/tools/model_consult/model_consult_tool/index.md` covering getting started, how mid-generation escalation works, `ModelConsultTool` and `ModelConsultContextConfig` options, custom context budgets, custom `BaseLlm` advisors, and limitations, and registers it in `docs/guides/README.md`.
- Adds a runnable e-commerce order support sample under `contributing/samples/tools/model_consult/` demonstrating `get_order`, `get_customer_profile`, and `issue_refund` paired with `ModelConsultTool`.

Co-authored-by: Xuan Yang <xygoogle@google.com>
PiperOrigin-RevId: 990615455

* fix: isolate and clean up single_turn LlmAgent node_input events

Merge google#7320

Fixes google#7227

PiperOrigin-RevId: 990632092

* refactor(tools): extract shared URL validation helpers into _url_validator.py

Extract the SSRF and URL target validation helpers (`_parse_request_target`,
`_is_blocked_hostname`, `_is_blocked_address`, `_embedded_ipv4`,
`_resolve_direct_addresses`, and `_reject_blocked_proxied_hostname`) into
`_url_validator.py`.

Previously, `ComputerUseToolset._wrap_navigate_with_url_validation`
lazily imported private helpers from `load_web_page` inside the wrapper
function to avoid pulling `requests` into the computer-use import path.
Moving the validation logic into `_url_validator.py` allows
`load_web_page`, `ComputerUseToolset`, and other outbound HTTP tools to
import the shared validation helpers directly at the module level
without extra runtime dependencies.

Test files that patched `load_web_page.socket` were updated after
private helpers were pruned from the `load_web_page` namespace.

Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 990692502

* fix(plugins): chain re-raised interrupts to nothing, look up handle per call

Review of the previous commit found two small hardening gaps:

- The KeyboardInterrupt or SystemExit set aside while a content_formatter
  failure was described is raised after the row with `from None`. That
  only sets __suppress_context__: Python still records the exception the
  caller is handling, such as the error ADK passes to an error callback,
  as its __context__, so code that walks __context__ could reach it and
  its text. It is now raised while a context-free stand-in is handled,
  so that stand-in is its only link.
- The stand-in wrapper bound Logger.handle when the plugin was imported,
  so a later class-level patch of Logger.handle, as instrumentation and
  test fixtures apply, never saw this logger's records. The class's
  handle is now looked up on each call, still inside the stand-in.

The content_formatter docstring also states the downstream effect
plainly: the row's status is left as the event set it, usually 'OK', so
a query that counts any non-NULL error_message as an error, such as the
BigQuery Agent Analytics SDK's error predicate, counts a formatter
failure as an error. Behavior is unchanged.

Tests: the re-raised interrupt links to neither the caller's exception
nor anything chained to it (KeyboardInterrupt and SystemExit), and a
class-level patch of Logger.handle made after import sees the plugin's
formatter warning, handled while the stand-in is. Both fail before this
change.

Refs: GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK#485 (item A)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: feiiiiii5 <feiiiiii5@users.noreply.github.com>
Co-authored-by: George Weale <gweale@google.com>
Co-authored-by: Aarav Mittal <137450929+a2105z@users.noreply.github.com>
Co-authored-by: Xuan Yang <xygoogle@google.com>
Co-authored-by: Shangjie Chen <deanchen@google.com>
Co-authored-by: Kathy Wu <wukathy@google.com>
Co-authored-by: Liang Wu <wuliang@google.com>
Co-authored-by: Shinobu Aoki <aoki@codebee.jp>
Co-authored-by: YASHcode-IIITV <yashrajsiwal@gmail.com>
Co-authored-by: Chaitanya Laxman <chaitanyalaxman118@gmail.com>
Co-authored-by: Arjun Ganesh <iarjunganesh@gmail.com>
Co-authored-by: Vishal Bulbule <vishal.bulbule@techtrapture.com>
Co-authored-by: Abhay Joshi <abhayjoshi@google.com>
Co-authored-by: Jason Zhang <jasoncz@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants