I asked Claude Code to build me a blog application for the Internet Computer with Internet Identity login and admin-only post creation. The docs said to use https://beta-docs.internetcomputer.org/llms.txt for reference. What followed was a painful debugging session that could have been avoided entirely — and the lessons are worth sharing.
The Task
Simple enough: a Rust backend canister with CRUD operations, a React/Vite frontend, Internet Identity authentication, and role-based access control where only admins can create posts.
What Actually Happened
Round 1: Claude Ignored the Skills
Claude fetched the llms.txt file, read several documentation pages, and started writing code from scratch. It did not use any of the ICP-specific agent skills that were available to it — icp-cli, internet-identity, canister-security, and others. These skills contain tested patterns, correct dependency versions, proper configuration formats, and common pitfalls. Instead, Claude relied on web-fetched docs (several of which were TODO placeholder pages) and its training data.
The result: every single file had issues.
Round 2: The Error Cascade
Here's every error we hit, in order:
-
Dependency conflict — ic-cdk = "0.19" alongside ic-cdk-timers = "0.11", which internally depends on ic-cdk = "0.17". The ic-cdk-executor native library link clashed.
-
TypeScript import.meta.env errors — Missing "types": ["vite/client"] in tsconfig.json, so TypeScript didn't know about Vite's ImportMeta extensions.
-
Wrong project structure — Claude created separate canister.yaml files in each canister directory, but icp.yaml with inline canister definitions is the correct pattern. The build commands then ran from the project root, not the canister directories.
-
Missing workspace Cargo.toml — The Rust recipe runs cargo build --package backend from the project root, which requires a workspace Cargo.toml. Claude only created one inside backend/.
-
Wrong npm --prefix paths — Frontend build commands pointed to app/ instead of frontend/app/ (since they run from the project root).
-
Wrong candid path — Specified backend.did instead of backend/backend.did in the recipe config (again, paths are relative to project root).
-
Wrong package versions — Used @icp-sdk/auth@^0.1.0 and @icp-sdk/core@^0.1.0, which don't exist. Actual versions were 5.0.0 and 5.1.0.
-
Wrong import paths — Used @icp-sdk/auth instead of @icp-sdk/auth/client, @icp-sdk/core instead of @icp-sdk/core/agent and @icp-sdk/core/principal. These packages use subpath exports.
-
tsc before bindings exist — The build script ran tsc && vite build, but TypeScript bindings are generated by the @icp-sdk/bindgen Vite plugin during the Vite build. Running tsc first meant the bindings module didn't exist yet.
-
Wrong binding import path — The bindgen plugin generates backend.ts, not index.ts. So ./bindings/backend didn't resolve — needed ./bindings/backend/backend.
-
Backend canister ID missing from ic_env — The backend was deployed in an earlier run before icp.yaml was restructured, so the frontend asset canister's environment variables didn't include PUBLIC_CANISTER_ID:backend. Had to restart the network and deploy both together.
-
fetch Illegal Invocation — HttpAgent internally calls fetch, but without window.fetch.bind(window), the function loses its this context in the minified production bundle.
-
IC_ROOT_KEY.match is not a function — safeGetCanisterEnv() from @icp-sdk/core already parses the root key into a Uint8Array. Claude tried to hex-decode it with .match(/.{1,2}/g) as if it were a string.
-
II login not working — No ii: true in the local network config, and the II URL was hardcoded to a wrong value.
-
CLI interactive prompts blocking — icp canister call waited for confirmation, hanging the terminal.
That's 15 errors across roughly 8 deploy attempts before reaching a working application.
What the Skills Would Have Prevented
After the initial failures, Claude loaded two ICP skills:
icp-cli skill
This skill contains:
- The correct
icp.yaml format with inline canister definitions
- Recipe configuration with proper version pins (
@dfinity/rust@v3.2.0)
- The
@icp-sdk/bindgen Vite plugin setup for TypeScript binding generation
- The
ic_env cookie mechanism (replacing dfx's .env files)
- Dev server configuration for simulating the
ic_env cookie locally
- A "Common Pitfalls" section that explicitly warns against most of the mistakes Claude made
internet-identity skill
This skill contains:
- The
ii: true network configuration for local development
- The correct
AuthClient import from @icp-sdk/auth/client
- How to read the II canister ID from
ic_env at runtime
- Proper
identityProvider URL construction for local vs mainnet
- The
maxTimeToLive delegation expiry pattern
Had these skills been loaded from the start, errors 1-14 would likely not have occurred. The skills contain the exact dependency versions, import paths, configuration formats, and common pitfalls that Claude got wrong.
The Discovery Gap: llms.txt Didn't Advertise Skills
Here's the key insight: the llms.txt file at beta-docs.internetcomputer.org links to 86 documentation pages. It also contains one reference to skills:
Agent skills for IC development: https://skills.internetcomputer.org/.well-known/skills/index.json
And a link to an agentic development guide:
https://beta-docs.internetcomputer.org/guides/tools/agentic-development.md
But that guide page is a TODO placeholder — it has no content. The skills index URL exists but isn't prominently featured or explained. There's no instruction in llms.txt telling an AI agent: "Before writing any ICP code, install and load the relevant skills from skills.internetcomputer.org."
The llms.txt standard is designed to help AI agents understand how to use a project's documentation. If the skills are the most reliable source of implementation patterns (which they are — they contain tested code, correct versions, and pitfall warnings), then llms.txt should make that explicit. Something like:
# IMPORTANT: Agent Skills
Before generating ICP code, load the relevant skills from:
https://skills.internetcomputer.org/.well-known/skills/index.json
Available skills include: icp-cli, internet-identity, canister-security,
stable-memory, icrc-ledger, and others. These contain tested patterns,
correct dependency versions, and common pitfalls that documentation
pages may not cover.
Install with: npx skills add dfinity/icskills
Without this, an AI agent does what Claude did: reads the docs (many of which are incomplete TODO pages), hallucinate patterns from training data, and produces code that fails in predictable ways.
Session Stats
- Errors encountered: 15
- Deploy attempts before working app: ~8
- Skills used: 2 (
icp-cli, internet-identity)
- Skills that should have been used from the start: at minimum
icp-cli and internet-identity
- Documentation pages that were TODO placeholders: 3 (Internet Identity integration, stable structures, frontend frameworks)
Takeaways
-
Skills > Docs for code generation. Documentation explains concepts. Skills contain implementation patterns with tested code, correct versions, and pitfall warnings. AI agents need the latter.
-
llms.txt should prioritize actionable guidance. Linking to 86 doc pages is useful for humans browsing. For AI agents, a clear directive to load skills first would save significant time and tokens.
-
The ICP ecosystem has the skills infrastructure. The skills.internetcomputer.org registry exists, npx skills add dfinity/icskills works, and the skills themselves are high quality. The missing piece is discovery — making sure AI agents find and use them before writing code.
-
Incomplete docs are worse than no docs for AI. When an AI fetches a page and gets "TODO: Write content for this page", it falls back to training data, which may be outdated. A llms.txt file should either exclude incomplete pages or mark them clearly.
The blog app works now. Posts load, Internet Identity login works, admin access control is enforced. But it took far longer than it should have — not because the tooling is bad, but because the AI agent wasn't guided to the right resources from the start.
I asked Claude Code to build me a blog application for the Internet Computer with Internet Identity login and admin-only post creation. The docs said to use
https://beta-docs.internetcomputer.org/llms.txtfor reference. What followed was a painful debugging session that could have been avoided entirely — and the lessons are worth sharing.The Task
Simple enough: a Rust backend canister with CRUD operations, a React/Vite frontend, Internet Identity authentication, and role-based access control where only admins can create posts.
What Actually Happened
Round 1: Claude Ignored the Skills
Claude fetched the
llms.txtfile, read several documentation pages, and started writing code from scratch. It did not use any of the ICP-specific agent skills that were available to it —icp-cli,internet-identity,canister-security, and others. These skills contain tested patterns, correct dependency versions, proper configuration formats, and common pitfalls. Instead, Claude relied on web-fetched docs (several of which were TODO placeholder pages) and its training data.The result: every single file had issues.
Round 2: The Error Cascade
Here's every error we hit, in order:
Dependency conflict —
ic-cdk = "0.19"alongsideic-cdk-timers = "0.11", which internally depends onic-cdk = "0.17". Theic-cdk-executornative library link clashed.TypeScript
import.meta.enverrors — Missing"types": ["vite/client"]intsconfig.json, so TypeScript didn't know about Vite'sImportMetaextensions.Wrong project structure — Claude created separate
canister.yamlfiles in each canister directory, buticp.yamlwith inline canister definitions is the correct pattern. The build commands then ran from the project root, not the canister directories.Missing workspace
Cargo.toml— The Rust recipe runscargo build --package backendfrom the project root, which requires a workspaceCargo.toml. Claude only created one insidebackend/.Wrong
npm --prefixpaths — Frontend build commands pointed toapp/instead offrontend/app/(since they run from the project root).Wrong
candidpath — Specifiedbackend.didinstead ofbackend/backend.didin the recipe config (again, paths are relative to project root).Wrong package versions — Used
@icp-sdk/auth@^0.1.0and@icp-sdk/core@^0.1.0, which don't exist. Actual versions were5.0.0and5.1.0.Wrong import paths — Used
@icp-sdk/authinstead of@icp-sdk/auth/client,@icp-sdk/coreinstead of@icp-sdk/core/agentand@icp-sdk/core/principal. These packages use subpath exports.tscbefore bindings exist — The build script rantsc && vite build, but TypeScript bindings are generated by the@icp-sdk/bindgenVite plugin during the Vite build. Runningtscfirst meant the bindings module didn't exist yet.Wrong binding import path — The bindgen plugin generates
backend.ts, notindex.ts. So./bindings/backenddidn't resolve — needed./bindings/backend/backend.Backend canister ID missing from
ic_env— The backend was deployed in an earlier run beforeicp.yamlwas restructured, so the frontend asset canister's environment variables didn't includePUBLIC_CANISTER_ID:backend. Had to restart the network and deploy both together.fetchIllegal Invocation —HttpAgentinternally callsfetch, but withoutwindow.fetch.bind(window), the function loses itsthiscontext in the minified production bundle.IC_ROOT_KEY.match is not a function—safeGetCanisterEnv()from@icp-sdk/corealready parses the root key into aUint8Array. Claude tried to hex-decode it with.match(/.{1,2}/g)as if it were a string.II login not working — No
ii: truein the local network config, and the II URL was hardcoded to a wrong value.CLI interactive prompts blocking —
icp canister callwaited for confirmation, hanging the terminal.That's 15 errors across roughly 8 deploy attempts before reaching a working application.
What the Skills Would Have Prevented
After the initial failures, Claude loaded two ICP skills:
icp-cliskillThis skill contains:
icp.yamlformat with inline canister definitions@dfinity/rust@v3.2.0)@icp-sdk/bindgenVite plugin setup for TypeScript binding generationic_envcookie mechanism (replacing dfx's.envfiles)ic_envcookie locallyinternet-identityskillThis skill contains:
ii: truenetwork configuration for local developmentAuthClientimport from@icp-sdk/auth/clientic_envat runtimeidentityProviderURL construction for local vs mainnetmaxTimeToLivedelegation expiry patternHad these skills been loaded from the start, errors 1-14 would likely not have occurred. The skills contain the exact dependency versions, import paths, configuration formats, and common pitfalls that Claude got wrong.
The Discovery Gap:
llms.txtDidn't Advertise SkillsHere's the key insight: the
llms.txtfile atbeta-docs.internetcomputer.orglinks to 86 documentation pages. It also contains one reference to skills:And a link to an agentic development guide:
But that guide page is a TODO placeholder — it has no content. The skills index URL exists but isn't prominently featured or explained. There's no instruction in
llms.txttelling an AI agent: "Before writing any ICP code, install and load the relevant skills fromskills.internetcomputer.org."The
llms.txtstandard is designed to help AI agents understand how to use a project's documentation. If the skills are the most reliable source of implementation patterns (which they are — they contain tested code, correct versions, and pitfall warnings), thenllms.txtshould make that explicit. Something like:Without this, an AI agent does what Claude did: reads the docs (many of which are incomplete TODO pages), hallucinate patterns from training data, and produces code that fails in predictable ways.
Session Stats
icp-cli,internet-identity)icp-cliandinternet-identityTakeaways
Skills > Docs for code generation. Documentation explains concepts. Skills contain implementation patterns with tested code, correct versions, and pitfall warnings. AI agents need the latter.
llms.txtshould prioritize actionable guidance. Linking to 86 doc pages is useful for humans browsing. For AI agents, a clear directive to load skills first would save significant time and tokens.The ICP ecosystem has the skills infrastructure. The
skills.internetcomputer.orgregistry exists,npx skills add dfinity/icskillsworks, and the skills themselves are high quality. The missing piece is discovery — making sure AI agents find and use them before writing code.Incomplete docs are worse than no docs for AI. When an AI fetches a page and gets "TODO: Write content for this page", it falls back to training data, which may be outdated. A
llms.txtfile should either exclude incomplete pages or mark them clearly.The blog app works now. Posts load, Internet Identity login works, admin access control is enforced. But it took far longer than it should have — not because the tooling is bad, but because the AI agent wasn't guided to the right resources from the start.