.claude/skills/continuous-learning/examples/typescript-circular-dependency/SKILL.md
Detect and resolve TypeScript/JavaScript circular import dependencies. Use when: (1) "Cannot access 'X' before initialization" at runtime, (2) Import returns undefined unexpectedly, (3) "ReferenceError: Cannot access X before initialization", (4) Type errors that disappear when you change import order, (5) Jest/Vitest tests fail with undefined imports that work in browser.
npx skillsauth add Dbochman/dotfiles typescript-circular-dependencyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Circular dependencies occur when module A imports from module B, which imports
(directly or indirectly) from module A. TypeScript compiles successfully, but at
runtime, one of the imports evaluates to undefined because the module hasn't
finished initializing yet.
Common error messages:
ReferenceError: Cannot access 'UserService' before initialization
TypeError: Cannot read properties of undefined (reading 'create')
TypeError: (0 , _service.doSomething) is not a function
Symptoms that suggest circular imports:
undefined even though the export existsconsole.log at the top of a file changes behaviorUse a tool to visualize dependencies:
# Install madge
npm install -g madge
# Find circular dependencies
madge --circular --extensions ts,tsx src/
# Generate visual graph
madge --circular --image graph.svg src/
Or use the TypeScript compiler:
# Check for cycles (requires tsconfig setting)
npx tsc --listFiles | head -50
Common circular dependency patterns:
Pattern A: Service-to-Service
services/userService.ts → services/orderService.ts → services/userService.ts
Pattern B: Type imports
types/user.ts → types/order.ts → types/user.ts
Pattern C: Index barrel files
components/index.ts → components/Button.tsx → components/index.ts
Strategy 1: Extract Shared Dependencies
Before:
// userService.ts
import { OrderService } from './orderService';
export class UserService { ... }
// orderService.ts
import { UserService } from './userService';
export class OrderService { ... }
After:
// types/interfaces.ts (new file - no imports from services)
export interface IUserService { ... }
export interface IOrderService { ... }
// userService.ts
import { IOrderService } from '../types/interfaces';
export class UserService implements IUserService { ... }
Strategy 2: Dependency Injection
// orderService.ts
export class OrderService {
constructor(private userService: IUserService) {}
// Instead of importing UserService directly
}
// main.ts
const userService = new UserService();
const orderService = new OrderService(userService);
Strategy 3: Dynamic Imports
// Only import when needed, not at module level
async function processOrder() {
const { UserService } = await import('./userService');
// ...
}
Strategy 4: Use Type-Only Imports
If you only need types (not values), use type-only imports:
// This doesn't create a runtime dependency
import type { User } from './userService';
Strategy 5: Restructure Barrel Files
Before (problematic):
// components/index.ts
export * from './Button';
export * from './Modal'; // Modal imports Button from './index'
After:
// components/Modal.tsx
import { Button } from './Button'; // Direct import, not from index
Add to your CI/build process:
// package.json
{
"scripts": {
"check:circular": "madge --circular --extensions ts,tsx src/"
}
}
Or configure ESLint:
// .eslintrc.js
module.exports = {
plugins: ['import'],
rules: {
'import/no-cycle': ['error', { maxDepth: 10 }]
}
}
madge --circular src/ - should report no cyclesnode_modules and reinstall - app should still workProblem: OrderService is undefined when imported in UserService
Detection:
$ madge --circular src/
Circular dependencies found!
src/services/userService.ts → src/services/orderService.ts → src/services/userService.ts
Fix: Extract shared interface
// NEW: src/types/services.ts
export interface IOrderService {
createOrder(userId: string): Promise<Order>;
}
// MODIFIED: src/services/userService.ts
import type { IOrderService } from '../types/services';
export class UserService {
constructor(private orderService: IOrderService) {}
}
// MODIFIED: src/services/orderService.ts
// No longer imports UserService
export class OrderService implements IOrderService {
async createOrder(userId: string): Promise<Order> { ... }
}
import type is your friend—it's erased at runtime and can't cause cyclesindex.ts) are a common source of accidental cyclesrequire() can sometimes mask circular dependency issues that import exposesdevelopment
Search the web for current information, news, facts, and answers. Use when asked questions about current events, needing to look something up, finding websites, researching topics, or when you need up-to-date information beyond your training data.
development
Summarize any URL, YouTube video, podcast, PDF, or file into concise text. Use when asked to read an article, summarize a link, get the gist of a video or podcast, extract content from a URL, or when you need to understand what a web page or document contains.
development
Play music via Spotify and control Google Home speakers. Use when asked to play music, songs, artists, playlists, podcasts, or control speakers/volume/audio.
testing
Create new OpenClaw skills, modify and improve existing skills, and measure skill performance with evals. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Also use when asked to "make a skill", "turn this into a skill", "improve this skill", or "test this skill".