#3749·artillery

`run-fargate` 2.0.33: esbuild dependency tracing adds `.` and `..` to module list, causing `EINVALIDPACKAGENAME` on workers

Author: sohhaofeng1989Created Jun 22, 2026Updated Jun 22, 2026

Version info:

artillery 2.0.33

Running this command:

bash
artillery run-fargate ./load-tests/load-test.yml \
  --output ./artillery-report/report.json \
  --dotenv .env.artillery.temp \
  --region ap-southeast-1 \
  --count 5 \
  --cluster my-ecs-cluster \
  --subnet-ids subnet-xxx \
  --security-group-ids sg-xxx

I expected to see this happen:

Workers sync successfully and the load test executes. This worked correctly on artillery 2.0.21 with the same project structure.

Instead, this happened:

The "Test bundle contents" table printed by the coordinator includes . and .. as packages (with note "not in package.json"):

┌────────────────────────────────────────┬─────────┬──────────────────────────────┐
│ Name                                   │ Type    │ Notes                        │
├────────────────────────────────────────┼─────────┼──────────────────────────────┤
│ load-tests/load-test.yml               │ file    │                              │
│ ...                                    │ file    │                              │
│ package.json                           │ file    │                              │
│ package-lock.json                      │ file    │                              │
├────────────────────────────────────────┼─────────┼──────────────────────────────┤
│ .                                      │ package │ not in package.json          │
│ ..                                     │ package │ not in package.json · v1.0.0 │
│ @datadog/datadog-ci                    │ package │                              │
│ ...                                    │ package │                              │
└────────────────────────────────────────┴─────────┴──────────────────────────────┘

The leader worker then attempts npm install .. and fails:

bash
******** [a519e2ac...] Worker starting up, ID = a519e2ac..., version = 2.0.33, leader = true
******** [a519e2ac...] Syncing test data
******** [a519e2ac...] Installing dependencies
Installing required npm dependencies
installing .
Protected by Socket Firewall
npm error code EINVALIDPACKAGENAME
npm error Invalid package name ".." of package "[email protected]": name cannot start with a period.

Because the leader crashes, it never produces node_modules.zip. All non-leader workers hang waiting for it:

******** [c929e234...] Worker starting up, ID = c929e234..., version = 2.0.33, leader = false
******** [c929e234...] Installing dependencies
Waiting... (s3://artilleryio-test-data-<account-id>/tests/<test-run-id>/node_modules.zip)

The coordinator eventually times out: Error: Timed out waiting for worker sync

Root Cause

The new getCustomJsDependencies function in lib/platform/aws-ecs/legacy/bom.js uses esbuild to trace imports from the processor file. The recoverPlugin catches relative imports that can't be resolved on disk and marks them as external:

javascript
build.onResolve({ filter: /^\.{1,2}\// }, (args) => {
  const candidate = path.resolve(args.resolveDir, args.path);
  for (const ext of exts) {
    try { if (fs.statSync(candidate + ext).isFile()) return null; } catch (_e) {}
  }
  // can't resolve → mark external
  return { path: args.path, external: true };
});

Later, extractPackageName extracts the first path segment as a "package name":

javascript
function extractPackageName(spec) {
  if (spec.startsWith('@')) { ... }
  return spec.split('/')[0]; // '../common/foo' → '..'  ,  './bar' → '.'
}

So when a relative import like '../common/network/types' can't be resolved (e.g. TypeScript file without explicit extension in a transitive dependency), it becomes .. in the modules list. Similarly './something' becomes ..

These get written into metadata.json and the leader worker runs npm install . .. which fails.

Suggested Fix

Filter out . and .. from the modules list in bom.js before it's returned:

javascript
const modules = _.uniq(context.npmModules).filter(
  (m) =>
    m !== 'artillery' &&
    m !== 'playwright' &&
    !m.startsWith('@playwright/') &&
    m !== '.' &&   // <-- add this
    m !== '..'     // <-- add this
);

Or guard in extractPackageName:

javascript
function extractPackageName(spec) {
  if (spec === '.' || spec === '..') return null;
  if (spec.startsWith('@')) { ... }
  return spec.split('/')[0];
}

Files being used:

yaml
# load-tests/load-test.yml
config:
  target: "https://example.com"
  engines:
    playwright:
      launchOptions:
        headless: true
      contextOptions:
        ignoreHTTPSErrors: true
      extendedMetrics: true
  processor: ./flows.ts
  phases:
    - name: ramp up phase
      rampTo: "{{ $env.A_VUSER_ARRIVAL_RATE }}"
      duration: "{{ $env.A_RAMP_UP_TIME }}"
      maxVusers: "{{ $env.A_MAX_VUSERS }}"

scenarios:
  - name: "login-scenario"
    engine: playwright
    flowFunction: "loginTask"
typescript
// load-tests/flows.ts (processor file)
import { type Page } from '@playwright/test'
import { type RpLoginInfo } from '../common/network/types'   // relative import going up one level
import { executeLoginTask } from '../common/task/tasks'       // same pattern
import { getClient } from '../common/globalSetUp'

export async function loginTask(page: Page, userContext: any, events: any, test: any): Promise<void> {
  // ...
}
# Project structure:
/app
├── load-tests/
│   ├── load-test.yml
│   └── flows.ts          ← processor, imports from ../common/
├── common/
│   ├── network/types.ts
│   ├── task/tasks.ts
│   └── globalSetUp.ts
├── package.json
└── package-lock.json

Additional Context

  • This did NOT occur on artillery 2.0.21 (which did not have esbuild-based dependency tracing)
  • The project uses TypeScript with relative imports across sibling directories (load-tests/../common/)
  • The issue is reproducible on every run with 2.0.33; it is not intermittent