Fix: log a warning when AgentLoader skips an unloadable agent file - #365
Open
AmaadMartin wants to merge 1 commit into
Open
Fix: log a warning when AgentLoader skips an unloadable agent file#365AmaadMartin wants to merge 1 commit into
AmaadMartin wants to merge 1 commit into
Conversation
AgentLoader swallowed AgentFileLoadingError in loadAgentFromFile and loadAgentFromDirectory with no output, so a broken agent file and a plain non-agent module in the agents directory were indistinguishable: the agent just never appeared in listAgents() and every request for it 404'd, with nothing in the logs pointing back at the file. Keep the swallow (a non-agent module may legitimately sit in the agents directory) but emit one logger.warn naming the source path and the error. The message prefixes file.path because AgentFile.load() reassigns its local filePath to the esbuild output before throwing, so e.message alone only names a temp artifact.
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem:
AgentLoaderswallowsAgentFileLoadingErrorin two places with no diagnostic output at all —loadAgentFromFileandloadAgentFromDirectory(dev/src/utils/agent_loader.ts) bothreturn;on it. A developer drops an agent file into their agents directory, runsadk web/adk api_server, and the agent simply is not in the list. Nothing is printed at any log level,listAgents()omits it,/apps/<name>/...404s, and the A2A route is never mounted — all far away from the real cause.Precisely two throw sites produce
AgentFileLoadingError, so these are the only failures currently silenced:Agent file <path> does not exists— thefsPromises.statENOENT path.Failed to load agent <path>: No @google/adk BaseAgent class instance found. ...— the common one: an empty file, a file that forgot toexportits agent, or arootAgentexport that is not aBaseAgent/App.Everything else is already loud and stays that way: an esbuild
BuildFailure(syntax error, unresolvable import) and any error thrown while evaluating the module are notAgentFileLoadingError, so they still hitthrow eand rejectpreloadAgents(). This PR does not change that.Solution: Keep the swallow and add one
logger.warnbefore eachreturn. The swallow itself is deliberate and correct —preloadAgents()walks every.js/.cjs/.mjs/.ts/.mts/.ctsfile at the top level of the agents directory, so a plain helper module living next to the agents must not abort discovery. The defect was that the swallow was silent, making a genuinely broken agent and an intentionally-not-an-agent file indistinguishable from the outside.Two considered alternatives were rejected: converting the swallow to a throw (breaks the mixed-directory use case), and adding an option to choose between the two (a flag for a log line). Log-and-keep-going is what
adk-pythonalready does insrc/google/adk/cli/utils/agent_loader.py("Failed to load agent '%s': %s"thencontinue); this is the TypeScript equivalent of that line, not a field-for-field parity port.Two details worth calling out:
AgentFile.load()reassigns its localfilePathto the esbuild output before throwing, soe.messagealone only names a temp artifact like/tmp/adk_agent_loader/<uuid>/broken_agent.cjs. Prefixing withfile.path/possibleEntryFile.pathis what makes the warning actionable, which is why the message has both parts. A test mutation below pins exactly this.preloadAgents()short-circuits onagentsAlreadyPreloaded, so the warnings are emitted once per process, re-emitted only ifinvalidateAll()fires under--reload_agents.Deliberately out of scope: the empty
catch {}inlistApps(), the silent earlyreturnfor a directory with noagent.*/app.*entrypoint (warning there would fire for every unrelated subdirectory, includingnode_modules), and the two existingconsole.warncalls inAgentFile. No new types, exports, options, helpers, or dependencies;AgentFileLoadingErrorstays unexported and noprivatemember was widened.Collision check (required before implementation): scanned all 269 open PRs on the fork. Five touch
dev/src/utils/agent_loader.ts— #309, #285, #328, #275, #264 — but every one of their hunks lands in theAgentFileclass / compile-and-bundle path or inisJsFile, and none touches theAgentFileLoadingErrorcatch blocks inloadAgentFromFile/loadAgentFromDirectory. No PR implements this change, so this branches frommainrather than stacking.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
Two new tests in
dev/test/utils/agent_loader_test.ts, one per new line. The pre-existinghandles AgentFileLoadingError in directory loadingtest was left untouched — it still pins the swallow (thatbad_agent_dirstays out oflistAgents()), and no existing test was modified, deleted, skipped, or weakened.AgentLoader's logger is a module-private instance, so the tests spy onAdkLogger.prototype.warnandmockRestore()at the end. They deliberately do not assert call counts — that prototype is shared (AgentFile.load()also warns about the require cache, and theagent1/agent2/agent3fixtures load in the same run), sotoHaveBeenCalledTimeswould be flaky. Each test instead selects the one warning whose text contains its own source path.Coverage of new code: both new statements are executed and asserted — v8 reports 1 hit on the
loadAgentFromFilewarn and 2 on theloadAgentFromDirectorywarn (the new test plus the pre-existing one). 100% line coverage on new code; no new branches were introduced, since both lines sit inside anif (e instanceof AgentFileLoadingError)block that was already covered.Proof the tests can fail — coverage is a floor, so each test was run against mutated source. Three mutations:
logger.warnlines. Both new tests fail:loadAgentFromFileline. Exactly one test fails (standalone agent file×,directory entrypoint✓), and vice-versa when only theloadAgentFromDirectoryline is deleted — so neither test covers the other's line.logger.warn(e.message)(dropping the source-path prefix). Both tests fail, confirming they pin the source path and not merely the error text — the property the fix exists for.Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
Run with no mocks against the built
devoutput:Observed — both broken files are named with their source paths, the server comes up normally, and the working agent is still served:
Note the warnings appear on the first request that triggers discovery, not at process boot —
preloadAgents()is lazy. No flag is needed for them to be visible:AdkLoggerdefaults toLogLevel.INFO, sowarnpasses its level check.Other checks on the pushed commit:
npm run ts:checkreports errors, but all of them are pre-existing onmain(verified by stashing this change and re-running) and live incore/test/**andtests/**. Neither file in this diff produces a type error.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.
CI note (pre-existing Windows/macOS flake, not this change)
All checks are green:
run-testson ubuntu-latest, macos-latest and windows-latest, pluscheck-license. The Windows leg failed on its first two attempts, and since a flaky-looking failure is exactly how a real regression hides, here is the evidence that it is not one:tests/integration/adk_web/webui_test.ts(CLI exited prematurely) +tests/integration/app_loader/app_loader_test.ts(timed out in 40000ms)core/test/code_executors/unsafe_local_code_executor_test.ts(timed out in 5000ms) — the two above passedcore/test/code_executors/, which this diff (two files, both underdev/) cannot reach.AgentFileLoadingErroris swallowed, and neither fixture can trigger one —tests/integration/app_loader/discovery/holds onlypackage.json,standalone_agent.ts,standalone_app.ts,service_alpha/app.ts,service_beta/agent.ts, andtests/integration/adk_web/agent/holds onlyagent.ts. All load successfully, so zero new warnings are emitted in either suite.app_loader_test.ts40s timeout fails on Windows on a sibling PR that does not touchagent_loaderat all, and a second sibling fails the same way on macOS.app_loader_test.ts6/6 (the discovery test in 18.9s, well inside its 40s budget) andwebui_test.ts2/2.