GoogleCloudPlatform / GoogleCloudPlatform/devrel-demos

prai-roadshow-lab-1-starter: `shared/` symlinks silently become text stubs on Windows clones

Open
#1,527 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.