GoogleCloudPlatform / GoogleCloudPlatform/devrel-demos
prai-roadshow-lab-1-starter: `shared/` symlinks silently become text stubs on Windows clones
- Dominant language
- Jupyter Notebook
- Stars
- 368
- Forks
- 178
- Avg merge
- 8h 44m
- Merged PRs (30d)
- 20
Description
The documented clone recipe for
[Production-Ready AI Roadshow, Lab 1](https://codelabs.developers.google.com/codelabs/production-ready-ai-roadshow/1-building-a-multi-agent-system/building-a-multi-agent-system#1)
produces a broken checkout on Windows, and it fails in a way that's hard to diagnose.
`prai-roadshow-lab-1-starter/` links three files from `shared/` into each agent
directory as symlinks (documented in its README). On Windows, git without
`core.symlinks=true` writes those as small **text files containing the relative target
path** rather than as links. No warning, no error — the clone reports success.
### Affected files (9)
```
agents/content_builder/a2a_utils.py -> ../../shared/a2a_utils.py
agents/content_builder/adk_app.py -> ../../shared/adk_app.py
agents/judge/a2a_utils.py -> ../../shared/a2a_utils.py
agents/judge/adk_app.py -> ../../shared/adk_app.py
agents/orchestrator/a2a_utils.py -> ../../shared/a2a_utils.py
agents/orchestrator/adk_app.py -> ../../shared/adk_app.py
agents/researcher/a2a_utils.py -> ../../shared/a2a_utils.py
agents/researcher/adk_app.py -> ../../shared/adk_app.py
app/authenticated_httpx.py -> ../shared/authenticated_httpx.py
```
Each is 23–25 bytes. For example `agents/judge/a2a_utils.py` contains exactly:
```
../../shared/a2a_utils.py
```
### Reproduction
On Windows 11, git 2.x, without Developer Mode enabled and without
`core.symlinks=true`:
```bash
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/GoogleCloudPlatform/devrel-demos.git temp-repo
cd temp-repo
git sparse-checkout set agents/build-with-ai/production-ready-ai/prai-roadshow-lab-1-starter
cd ..
mv temp-repo/agents/build-with-ai/production-ready-ai/prai-roadshow-lab-1-starter .
rm -rf temp-repo
wc -c prai-roadshow-lab-1-starter/agents/judge/adk_app.py
# 23 (expected ~8133)
```
`adk_app.py` is the container entrypoint (`CMD ["python3", "adk_app.py", ...]`), so the
failure surfaces at run time as a syntax/import error inside a 23-byte file, which is a
confusing place for a first-time lab user to land.
### Why `core.symlinks=true` alone isn't a sufficient answer
Creating file symlinks on Windows requires either Developer Mode or an elevated
process. On a stock Windows 11 install, `New-Item -ItemType SymbolicLink` fails with
*"Administrator privilege required for this operation."* So a user who sets
`core.symlinks=true` and re-clones may still not get working links, depending on their
machine policy.
### Suggested fixes (any one would do)
1. **Ship real files** instead of symlinks. The three shared modules total ~19 KB;
duplicating them across four agents plus the app costs very little and removes the
platform dependency entirely.
2. **Make `shared/` an installable local package** and depend on it from each agent's
`pyproject.toml` — arguably the cleaner fix given `uv` is already in use.
3. **Document the Windows prerequisite** in the codelab (enable Developer Mode, set
`core.symlinks=true`) and add a post-clone verification step.
### Workaround for other Windows users
NTFS hardlinks give the same shared-content semantics as the intended symlinks and need
no elevation:
```powershell
$root = "path\to\prai-roadshow-lab-1-starter"
foreach ($a in @('content_builder','judge','orchestrator','researcher')) {
foreach ($f in @('a2a_utils.py','adk_app.py')) {
$lp = "$root\agents\$a\$f"
Remove-Item $lp -Force
New-Item -ItemType HardLink -Path $lp -Target "$root\shared\$f" | Out-Null
}
}
Remove-Item "$root\app\authenticated_httpx.py" -Force
New-Item -ItemType HardLink -Path "$root\app\authenticated_httpx.py" `
-Target "$root\shared\authenticated_httpx.py" | Out-Null
```
Caveat: hardlinks break silently if a tool replaces rather than edits a file.
---
### Second, unrelated defect: README's agent-card URL is wrong
`README.md` (deployment step 2) documents the frontend env vars as:
```
RESEARCHER_AGENT_CARD_URL: https:///a2a/agent/.well-known/agent.json
```
But both `deploy.sh` (lines 68–70) and `run_local.sh` (lines 31–33) use
`.well-known/**agent-card**.json`. Following the README when wiring these by hand
yields a 404 from a path that looks plausible. Suggest correcting the README to
`agent-card.json`.
---
### Secondary observation — possibly intentional
`agents/orchestrator/authenticated_httpx.py` is a **real file**, not a symlink, and
diverges from `shared/authenticated_httpx.py`:
```diff
+ from google.adk.agents.remote_a2a_agent import DEFAULT_TIMEOUT
- DEFAULT_TIMEOUT = 600.0
```
The orchestrator version imports the constant from ADK; the shared version hardcodes
`600.0`. Meanwhile `app/authenticated_httpx.py` *is* symlinked to the shared copy. If
the divergence is deliberate, ignore this; flagging in case the shared copy is stale.
---
Environment: Windows 11 Pro 26200, git 2.x via Git for Windows, Developer Mode off,
non-elevated shell. Google Cloud SDK 579.0.0, uv 0.12.0.
Contributor guide
Research direction
Start with the documented clone recipe and inspect the nine affected paths under prai-roadshow-lab-1-starter, then compare README.md with deploy.sh lines 68–70 and run_local.sh lines 31–33. Done means a supported Windows checkout contains usable shared modules and the README documents the same agent-card URL used by the scripts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, python
- Domain
- devtools, documentation, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100