microsoft / microsoft/vscode-azureresourcegroups
Scaffolded Functions project builds green but cannot start: tsconfig path alias emits an unresolvable runtime specifier
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 78
- Forks
- 55
- Avg merge
- 16h 49m
- Merged PRs (30d)
- 139
Description
For a coding agent: the fix is in this repo, in the scaffold agent instructions — not in any generated project. Jump to Where to fix it.
Summary
The scaffold agent emits a monorepo whose Functions workspace imports shared code through a tsconfig paths alias (@shared/*). TypeScript resolves that at compile time and does not rewrite module specifiers on emit, so the emitted JavaScript still asks Node for a package that does not exist. The project builds clean, type-checks clean, and the Functions worker dies on load.
This violates a rule the product documentation already states — it is simply not enforced anywhere.
Evidence
Found by the MSBench eval suite in run 2026083057881445 (stack react-functions-postgres, phase local).
validate-project-builds passes:
[project-builds] services/functions: npm ci
[project-builds] services/functions: npm run build
[project-builds] built 3 package(s): services/functions, services/shared, services/web
PASS: gate=project-builds — scaffolded project installs and builds
The app then fails to start:
Azure Functions Core Tools
Core Tools Version: 4.14.0+4a17060ecc915f1672d86717a487ace30f535e74 (64-bit)
Function Runtime Version: 4.1052.200.26352
[...] Worker was unable to load entry point "dist/functions/src/functions/createTask.js":
Cannot find module '@shared/schemas/index'
Require stack:
- /workspace/services/functions/dist/functions/src/functions/createTask.js
- /usr/lib/azure-functions-core-tools-4/workers/node/dist/src/worker-bundle.js
All five runtime-* gates fail as a cascade of this one root cause.
Root cause
The generated project contains two competing mechanisms for the same shared code.
services/shared/package.json is correct and well-formed, with real subpath exports:
{
"name": "@task-tracker/shared",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
"./types": { "types": "./dist/types/index.d.ts", "default": "./dist/types/index.js" },
"./schemas": { "types": "./dist/schemas/index.d.ts", "default": "./dist/schemas/index.js" }
}
}
But the Functions code never imports that package. It goes through an alias pointing at the shared package's source:
// services/functions/tsconfig.json
"paths": { "@shared/*": ["shared/src/*"] }
import { createTaskSchema } from '@shared/schemas/index';
import type { Task, CreateTaskDto } from '@shared/types/index';
tsc resolves @shared/* and emits require('@shared/schemas/index') verbatim. At runtime there is no @shared package in node_modules and no runtime path resolver — no tsconfig-paths, no bundler, no imports map — so Node cannot resolve it.
The correct specifier for the package that was actually generated is @task-tracker/shared/schemas; the documented convention is the relative form ../shared/....
The rule this breaks already exists
resources/agents/shared-references/architecture.md states it twice, in the naming callout:
Whatever names you pick, apply them consistently everywhere — npm
workspaces,cdcommands, tsconfigrootDir, and the computedmainfield […] Imports of the shared package stay../shared/....
and prescribes the supported mechanism under TypeScript Cross-Workspace Import Configuration:
When Functions imports from
../shared/,tsconfig.jsonmust setrootDirto reach outside workspace
// services/functions/tsconfig.json
{
"compilerOptions": { "rootDir": "..", "outDir": "dist" },
"include": ["src/**/*.ts", "../shared/**/*.ts"]
}
The generated project did follow the rootDir half — the emitted path is dist/functions/src/functions/createTask.js, exactly the documented rootDir: ".." shape — and then imported by alias anyway. Every code example in the instructions uses relative imports ('../../services/shared/types/entities'). No file in resources/agents/ mentions paths or @shared at all, so the alias is invented by the model, and nothing tells it not to.
Why the existing checkpoints miss it
resources/agents/azure-project-scaffold/instructions.md, Step 2 checkpoint block:
| # | Checkpoint | Why it passes anyway |
|---|---|---|
| 3 | Shared package has exports/main, builds to dist/ |
True here. The package is fine; nothing imports it. |
| 4 | Cross-workspace imports (CRITICAL): run tsc --noEmit, fix TS2307 |
The alias is precisely what makes tsc succeed. This check can never fire on this defect. |
| 5 | rootDir/main match actual dist/ output |
True here. |
| 6 | Production install must satisfy every import in dist/ |
Closest in spirit, but framed as a dependencies/devDependencies split, so it reads as "is the package in the right section" rather than "is this specifier resolvable at all". |
Checkpoint 4 is the one that looks like it covers this and does not. Every check is a compile-time check, and this is a defect that only exists after emit.
Where to fix it
1. resources/agents/azure-project-scaffold/instructions.md — Step 2 checkpoint block (the ✅ Checkpoint list containing items 1–6 and the ⚠️ Pitfalls line).
Add a checkpoint that inspects emitted output, not source. Suggested wording:
Emitted specifiers resolve (CRITICAL):
tscdoes not rewrite module specifiers. Any non-relative import that only resolved through a tsconfigpathsalias will still be indist/and will fail at runtime withCannot find module. After building, verify every non-relative specifier indist/resolves from that workspace. Do not usepathsaliases for cross-workspace imports — import the shared package by itsname, or use the relative../shared/...form.
Add to the ⚠️ Pitfalls line: paths aliases resolve at compile time only → Cannot find module at runtime.
2. resources/agents/shared-references/architecture.md — TypeScript Cross-Workspace Import Configuration section.
The "imports stay ../shared/..." rule currently lives inside a callout about folder naming, which is not where anyone looks for import mechanics. State it in this section as a rule with its reason, and show the alias as an explicit anti-pattern:
// ❌ compiles, fails at runtime — tsc emits require('@shared/schemas/index') unchanged
{ "compilerOptions": { "paths": { "@shared/*": ["shared/src/*"] } } }
Non-goal: do not add tsconfig-paths or a bundler to make the alias work. The supported mechanisms are the relative import and the workspace package name; adding a third would leave the same two competing mechanisms in place.
Verification command
Cheap, dependency-free, and would have caught this. Run from the workspace that emitted dist/:
node -e "
const fs=require('fs'),path=require('path'),Module=require('module');
const bad=[];
(function walk(d){ for(const e of fs.readdirSync(d,{withFileTypes:true})){
const p=path.join(d,e.name);
if(e.isDirectory()) walk(p);
else if(e.name.endsWith('.js')) for(const m of fs.readFileSync(p,'utf8').matchAll(/require\(['\\\"]([^'\\\"]+)['\\\"]\)/g)){
const s=m[1];
if(s.startsWith('.')||s.startsWith('node:')||Module.builtinModules.includes(s)) continue;
try { Module.createRequire(p).resolve(s); } catch { bad.push(p+' -> '+s); }
}}})('dist');
if(bad.length){ console.error('unresolvable specifiers in emitted output:'); bad.forEach(b=>console.error(' '+b)); process.exit(1); }
console.log('all emitted specifiers resolve');
"
On the run above this prints dist/functions/src/functions/createTask.js -> @shared/schemas/index and exits 1.
How to confirm the fix
The eval suite reproduces this end to end. From evals/:
BENCHMARK=corbench.cor_functions_host bash msbench/run.sh --skip-build \
--stack react-functions-postgres --phase local \
--dataset evals/msbench/container/dataset.jsonl
npm run analyze-run -- <run-id> # per-gate verdicts
Fixed looks like runtime-app-starts passing and runtime-crud completing a round-trip. The datastore emulators are already running in this image, so a failure there will be about the app, not the environment — gates-selftest-emulators proves the emulator path independently (run 2026083068733550, runtime-crud green against a preamble-started PostgreSQL).
Not the cause
The environment was healthy for the run that found this:
func already present: 4.14.0
azurite: listening on 127.0.0.1:10000
postgres: ready on 127.0.0.1:5432 (role taskuser, db tasktracker)
Run link: https://msbenchapp.azurewebsites.net/run-analysis/2026083057881445 — verified as a genuine result (156 /v1/messages calls, claude-sonnet-4.5 requested and active, not throttled).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Step 2 checkpoint block in resources/agents/azure-project-scaffold/instructions.md and the TypeScript Cross-Workspace Import Configuration section in resources/agents/shared-references/architecture.md. Review the emitted-specifier verification command, then update both documents to explain the runtime limitation and supported import forms. Done means the generated project passes runtime-app-starts and runtime-crud, with no unresolved specifiers in dist/.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, node.js, typescript
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100