Needs confirmation before acting — this follows from the template plus cookie scoping rules, I have not reproduced it. Filing it because if it holds, it bites more often than the duplicate-cookie case it was found alongside (dfinity/icp-js-core#1384).
The claim
All three frontend templates set the environment via a Set-Cookie response header on the Vite dev server:
hello-world/react-frontend/vite.config.ts:52 (identically in vue-frontend and svelte-frontend):
const server = {
headers: {
"Set-Cookie": `ic_env=${encodeURIComponent(
`PUBLIC_CANISTER_ID:${CANISTER_NAME}=${canisterId}&ic_root_key=${rootKey}`
)}; SameSite=Lax;`,
},
...
Cookies provide no isolation by port (RFC 6265 §1: "cookies for a given host are shared across all the ports on that host"). So a dev server on localhost:5173 and another on localhost:5174 write to the same jar. With no Domain these are host-only cookies on localhost, and with no Path the default-path for a request to / is /.
Same name, same host, same path means the second write doesn't sit alongside the first — it replaces it. Two scaffolded projects running dev servers concurrently would therefore clobber each other, and whichever started last wins for both. The first project's tab reads the second project's PUBLIC_CANISTER_ID:backend and root key, so its calls go to a canister from an unrelated project.
Why no attribute change fixes it
Path=/ doesn't help — both are already at /, and that's what you want anyway.
Partitioned doesn't help — the partition key is the top-level site, and both are localhost.
- Port cannot be expressed in any cookie attribute at all.
The transport is the problem, not its attributes. Deployed frontends are unaffected: those are served at http://<canister-id>.localhost:8000/, distinct hosts, correctly isolated. This is specific to the dev-server simulation of the asset canister's cookie.
Suggested direction
Have the dev server inject the environment through Vite's define (or import.meta.env) instead of a cookie. That's per-server by construction, so it's port-scoped for free, and it removes an ambient mutable value from local development. The trade-off is that the dev path stops being byte-identical to the deployed path, where the cookie is genuinely the right mechanism because the asset canister has no build step to inject into. @icp-sdk/core/agent/canister-env would still be the reader in production; only the dev-server simulation changes.
If keeping the cookie is preferred, the template could at least namespace it per project (ic_env_<project>) via the existing cookieName option on getCanisterEnv, which converts a silent wrong-canister into a clean "not found".
Repro sketch (unverified)
- Scaffold two projects from
hello-world, deploy both to a local network.
npm run dev in each — Vite picks 5173 and 5174.
- Open both in the same browser.
- Check
document.cookie on each; expect a single ic_env carrying the later project's canister id.
- Expect the first project's calls to target the wrong backend.
Worth running before deciding anything. If the collision doesn't reproduce, close this.
Context
Found while scoping dfinity/icp-js-core#1384 (getCanisterEnv taking the first of several ic_env cookies). That one is being fixed in dfinity/icp-js-core#1386, but the fix does not cover this: here there is only ever one cookie, so nothing conflicts — the single value is simply the wrong project's.
The same pattern appears in ~35 vite.config.js files in dfinity/examples and one in dfinity/icp-cli/examples/, so whatever is decided here should propagate there.
Needs confirmation before acting — this follows from the template plus cookie scoping rules, I have not reproduced it. Filing it because if it holds, it bites more often than the duplicate-cookie case it was found alongside (dfinity/icp-js-core#1384).
The claim
All three frontend templates set the environment via a
Set-Cookieresponse header on the Vite dev server:hello-world/react-frontend/vite.config.ts:52(identically invue-frontendandsvelte-frontend):Cookies provide no isolation by port (RFC 6265 §1: "cookies for a given host are shared across all the ports on that host"). So a dev server on
localhost:5173and another onlocalhost:5174write to the same jar. With noDomainthese are host-only cookies onlocalhost, and with noPaththe default-path for a request to/is/.Same name, same host, same path means the second write doesn't sit alongside the first — it replaces it. Two scaffolded projects running dev servers concurrently would therefore clobber each other, and whichever started last wins for both. The first project's tab reads the second project's
PUBLIC_CANISTER_ID:backendand root key, so its calls go to a canister from an unrelated project.Why no attribute change fixes it
Path=/doesn't help — both are already at/, and that's what you want anyway.Partitioneddoesn't help — the partition key is the top-level site, and both arelocalhost.The transport is the problem, not its attributes. Deployed frontends are unaffected: those are served at
http://<canister-id>.localhost:8000/, distinct hosts, correctly isolated. This is specific to the dev-server simulation of the asset canister's cookie.Suggested direction
Have the dev server inject the environment through Vite's
define(orimport.meta.env) instead of a cookie. That's per-server by construction, so it's port-scoped for free, and it removes an ambient mutable value from local development. The trade-off is that the dev path stops being byte-identical to the deployed path, where the cookie is genuinely the right mechanism because the asset canister has no build step to inject into.@icp-sdk/core/agent/canister-envwould still be the reader in production; only the dev-server simulation changes.If keeping the cookie is preferred, the template could at least namespace it per project (
ic_env_<project>) via the existingcookieNameoption ongetCanisterEnv, which converts a silent wrong-canister into a clean "not found".Repro sketch (unverified)
hello-world, deploy both to a local network.npm run devin each — Vite picks5173and5174.document.cookieon each; expect a singleic_envcarrying the later project's canister id.Worth running before deciding anything. If the collision doesn't reproduce, close this.
Context
Found while scoping dfinity/icp-js-core#1384 (
getCanisterEnvtaking the first of severalic_envcookies). That one is being fixed in dfinity/icp-js-core#1386, but the fix does not cover this: here there is only ever one cookie, so nothing conflicts — the single value is simply the wrong project's.The same pattern appears in ~35
vite.config.jsfiles indfinity/examplesand one indfinity/icp-cli/examples/, so whatever is decided here should propagate there.