Skip to content

fix(cli): honour absolute paths in adk run - #651

Merged
kalenkevich merged 2 commits into
mainfrom
fix/cli-absolute-paths
Aug 12, 2026
Merged

fix(cli): honour absolute paths in adk run#651
kalenkevich merged 2 commits into
mainfrom
fix/cli-absolute-paths

Conversation

@kalenkevich

Copy link
Copy Markdown
Collaborator

Link to Issue or Description of Change

2. Or, if no issue exists, describe the change:

Problem:

adk run rebases absolute paths onto the working directory, because
path.join strips the leading separator:

path.join('/Users/me/project', '/tmp/replay.json')
// => '/Users/me/project/tmp/replay.json'

So an absolute agent path fails with an error quoting a path the user never
typed:

$ npm run sample -- /tmp/samples/routes/sequence/agent.ts
AgentFileLoadingError: Agent file
/private/tmp/adk-fixes/tmp/samples/routes/sequence/agent.ts does not exists

and an absolute --replay path fails the same way:

$ npm run sample -- <agent> --replay /tmp/replay.json
Failed to read or parse file /Users/me/project/tmp/replay.json:
Error: ENOENT: no such file or directory

Both messages point at a path that is neither what was passed nor anywhere the
user can act on, which makes this disproportionately confusing to debug. It
also affects the --save_session destination.

Solution:

Resolve command-line paths with path.resolve instead of path.join, via a
small named helper in cli_run.ts so the reason is documented in one place.
path.resolve returns an absolute path unchanged and still resolves a relative
one against the working directory, so relative paths behave exactly as before.

--resume was already correct and is untouched: it hands its path straight to
loadFileData.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

Three tests in dev/test/cli/cli_run_test.ts: an absolute --replay path, an
absolute agent path, and a relative path that must keep resolving against cwd.
Confirmed the two absolute-path tests fail without the source change:

# with dev/src/cli/cli_run.ts stashed
Tests  2 failed | 16 passed (18)

# with the fix
Tests  18 passed (18)

Full dev project:

npx vitest run --project unit:dev
  Tests  1 failed | 261 passed (262)

The single failure is cli_create_test.ts > should handle Vertex AI selection with gcloud defaults, which reads local gcloud config and fails identically on
clean main at 5742875. Unrelated to this change.

Manual End-to-End (E2E) Tests:

$ echo "hello world" | npm run sample -- /tmp/adk-fixes/samples/workflows/routes/sequence/agent.ts
[task_A_node]: Summary: hello world
[task_B_node]: SUMMARY: HELLO WORLD
[task_C_node]: SUMMARY: HELLO WORLD (done)
[sequential_workflow]: SUMMARY: HELLO WORLD (done)

Relative paths continue to work unchanged.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Found while bug-bashing the graph-workflow docs samples: the human-in-the-loop
samples need --replay to script their two turns, and an absolute path to the
replay file silently failed.

A companion PR fixes two graph-workflow diagnostics found in the same session.

Generated with CloudCode, session ses_00c664b88ffeuSsmLI4zfs0a3q.

@kalenkevich

Copy link
Copy Markdown
Collaborator Author

The red zizmor-output here is not from this PR — it is an unpinned-uses finding in .github/workflows/validation.yaml on main, and this branch touches no workflow file. zizmor scans every workflow, so the gate fails on any PR that does not itself fix the base branch.

#652 pins those three actions and its zizmor-output is green, which confirms the cause. Once #652 lands, a re-run here should go green with no change to this PR.

The test matrix on this PR is passing on all three OSes.

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is correct. path.resolve keeps an absolute path on POSIX and on Windows, and the new tests build their absolute path from path.sep, so run-tests (windows-latest) is a real guard here and it passes. Two nits below, both optional; neither blocks. The one red check, zizmor-output, reports unpinned actions in .github/workflows/validation.yaml, a file this PR does not touch; it is red on #649 and #650 too, and #652 fixes it.

Comment thread dev/src/cli/cli_run.ts Outdated
* `<cwd>/tmp/x.json`. Every path here comes from the command line, so an
* absolute one has to be honoured as given.
*/
function resolveFromCwd(p: string): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, optional. The repo already has this helper: getAbsolutePath at dev/src/cli/cli.ts:53.

return path.isAbsolute(p) ? p : path.join(process.cwd(), p);

It wraps four path arguments (cli.ts:237, 283, 442, 493), but not run. Two more sites keep the raw form: cli_create.ts:186 and :203. That gives three shapes for one operation. Please share one helper under one name.

Keep the fix inside runAgent. getAbsolutePath at cli.ts:375 breaks cli_test.ts:233 and :256, and it misses --replay.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and thanks for naming the exact sites.

getAbsolutePath now lives in dev/src/utils/file_utils.ts and is the only spelling of this operation left in the CLI:

  • cli.ts no longer defines its own copy; it imports the shared one, so its four call sites are unchanged in behaviour.
  • cli_run.ts uses it for the agent path, --replay, and the save destination.
  • cli_create.ts:186 and :203 no longer build path.join(process.cwd(), ...) by hand, and the module-level dirname is gone.

The fix stays inside runAgent as you asked, so cli_test.ts still sees the raw agent.ts argument and needed no changes.

One deliberate exception: the two remaining process.cwd() uses in cli.ts are commander option defaults for --agents_dir/--tests_dir. Those are already absolute and never user-supplied strings, so wrapping them would be noise.

Comment thread dev/src/cli/cli_run.ts Outdated
sessionId: session.id,
});
await saveToFile(path.join(dirname, sessionPath), sessionToStore);
await saveToFile(resolveFromCwd(sessionPath), sessionToStore);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, optional, and pre-existing. The save destination starts from the agent file path at line 406:

const sessionPath = path.join(
  options.agentPath,
  `${sessionId}.session.json`,
);

For adk run agent.ts --save_session that is <cwd>/agent.ts/<id>.session.json. saveToFile runs no mkdir, and agent.ts is a file, so the write fails with ENOTDIR. join is the right call here; the base is wrong. Use path.dirname(options.agentPath). cli_run_test.ts:328 asserts only stringContaining('my-session.session.json'), so it stays green either way.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed. The base is now path.dirname(options.agentPath), so adk run agent.ts --save_session writes <cwd>/<id>.session.json next to the agent instead of attempting <cwd>/agent.ts/<id>.session.json and failing with ENOTDIR.

You were right that the existing assertion could not tell the difference. cli_run_test.ts now pins the whole resolved path rather than stringContaining('my-session.session.json'), so a regression to the old base fails the test:

expect(saveToFile).toHaveBeenCalledWith(
  path.join(process.cwd(), 'agents', 'my-session.session.json'),
  expect.anything(),
);

Taking it in this PR rather than leaving it: it is the same path-resolution bug the PR is about, and the diff already touches that line.

@AmaadMartin

Copy link
Copy Markdown
Collaborator

Correction to my review body: I wrote that #652 fixes the red zizmor-output check. That is wrong, and I withdraw the pointer.

#648 landed the three SHA pins in .github/workflows/validation.yaml on main at 82c7b600. This branch starts at 5742875, before that commit, so its tree still carries actions/checkout@v6, actions/setup-node@v6, and actions/setup-python@v5 at lines 21, 24 and 27 — the three lines zizmor reports. Current main has them pinned at lines 23, 28 and 31. A rebase onto current main clears the check.

My approval stands. The check stays unrelated to your diff.

@kalenkevich
kalenkevich force-pushed the fix/cli-absolute-paths branch 2 times, most recently from 675df89 to 8dfca56 Compare August 12, 2026 06:33
`path.join(process.cwd(), p)` strips the leading separator from an
absolute path, so `adk run /abs/agent.ts` looked for
`<cwd>/abs/agent.ts` and failed with a confusing "does not exists" that
quotes a path the user never typed. The same applied to `--replay`.

Resolve with `path.resolve` instead, which returns an absolute path
unchanged and still resolves a relative one against the working
directory. The existing `getAbsolutePath` from cli.ts moves to
file_utils, so `run` shares one helper with `web`, `api_server` and
`deploy` rather than open-coding a third shape of the same operation.

`--save_session` was broken outright, for a neighbouring reason: the
destination was joined onto the agent *file* path, giving
`<cwd>/agent.ts/<id>.session.json`, and since saveToFile does no mkdir
every save failed with ENOTDIR. Base it on the agent file's directory
so the session lands beside the agent.

`--resume` was already correct: it hands its path straight to
loadFileData.

Found while bug-bashing the graph-workflow docs samples.
@kalenkevich

Copy link
Copy Markdown
Collaborator Author

Rebased onto latest main (8611219). Thanks for the review — both nits taken, and the second one was a real bug.

Shared helpergetAbsolutePath moved from cli.ts into dev/src/utils/file_utils.ts, now switched to path.resolve, and used by both cli.ts and cli_run.ts. The fix stays inside runAgent as you suggested, so cli_test.ts:233/:256 are untouched and --replay is still covered. (cli_create.ts resolves an agent name rather than a user-supplied path, so I left it alone rather than change its behaviour.)

--save_session — confirmed broken, exactly as you described:

Failed to write file /private/tmp/adk-fixes/.bugbash/probes/r2_single.ts/my-session.session.json:
Error: ENOTDIR: not a directory

Not just the wrong location — the feature could never have worked, since the base was always a file. Fixed with path.dirname(options.agentPath) and covered by a test asserting the exact destination (the existing stringContaining assertion passed either way, as you noted). Verified end to end: Session saved to .bugbash/probes/my-session.session.json.

Note the zizmor-output pointer in your review body is moot now — #634 landed the pins on main, a maintainer closed #652 as redundant, and after the rebase the zizmor checks are skipped on this PR.

Dev suite: 265 tests, one pre-existing cli_create gcloud failure that also fails on clean main.

The last two raw `path.join(process.cwd(), ...)` sites in the CLI, so one
operation is spelled one way across cli, cli_run and cli_create.
@kalenkevich
kalenkevich merged commit 4ca10c8 into main Aug 12, 2026
12 checks passed
@kalenkevich
kalenkevich deleted the fix/cli-absolute-paths branch August 12, 2026 07:08
kalenkevich added a commit that referenced this pull request Aug 12, 2026
* fix(cli): honour absolute paths in adk run

`path.join(process.cwd(), p)` strips the leading separator from an
absolute path, so `adk run /abs/agent.ts` looked for
`<cwd>/abs/agent.ts` and failed with a confusing "does not exists" that
quotes a path the user never typed. The same applied to `--replay`.

Resolve with `path.resolve` instead, which returns an absolute path
unchanged and still resolves a relative one against the working
directory. The existing `getAbsolutePath` from cli.ts moves to
file_utils, so `run` shares one helper with `web`, `api_server` and
`deploy` rather than open-coding a third shape of the same operation.

`--save_session` was broken outright, for a neighbouring reason: the
destination was joined onto the agent *file* path, giving
`<cwd>/agent.ts/<id>.session.json`, and since saveToFile does no mkdir
every save failed with ENOTDIR. Base it on the agent file's directory
so the session lands beside the agent.

`--resume` was already correct: it hands its path straight to
loadFileData.

Found while bug-bashing the graph-workflow docs samples.

* fix(cli): route agent-dir creation through the shared path helper

The last two raw `path.join(process.cwd(), ...)` sites in the CLI, so one
operation is spelled one way across cli, cli_run and cli_create.
@AmaadMartin AmaadMartin mentioned this pull request Aug 20, 2026
prasanna8585 pushed a commit to prasanna8585/adk-js that referenced this pull request Aug 21, 2026
* fix(cli): honour absolute paths in adk run

`path.join(process.cwd(), p)` strips the leading separator from an
absolute path, so `adk run /abs/agent.ts` looked for
`<cwd>/abs/agent.ts` and failed with a confusing "does not exists" that
quotes a path the user never typed. The same applied to `--replay`.

Resolve with `path.resolve` instead, which returns an absolute path
unchanged and still resolves a relative one against the working
directory. The existing `getAbsolutePath` from cli.ts moves to
file_utils, so `run` shares one helper with `web`, `api_server` and
`deploy` rather than open-coding a third shape of the same operation.

`--save_session` was broken outright, for a neighbouring reason: the
destination was joined onto the agent *file* path, giving
`<cwd>/agent.ts/<id>.session.json`, and since saveToFile does no mkdir
every save failed with ENOTDIR. Base it on the agent file's directory
so the session lands beside the agent.

`--resume` was already correct: it hands its path straight to
loadFileData.

Found while bug-bashing the graph-workflow docs samples.

* fix(cli): route agent-dir creation through the shared path helper

The last two raw `path.join(process.cwd(), ...)` sites in the CLI, so one
operation is spelled one way across cli, cli_run and cli_create.
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.

3 participants