DEV Community

Mzer Terdoo
Mzer Terdoo

Posted on Originally published at Medium on

A Node.js Bug That Taught Me About the Current Working Directory (CWD)

A Node.js Bug That Taught Me About the Current Working Directory (CWD)

There are bugs that make you question your code, and then there are bugs that make you question your understanding of how a language works.

This was the latter.

Recently, while wiriting a script in a project I have been on for quite a while now, I encountered an issue that opened my eyes to understanding the concept of Current Working Directory. Interestingly, I only got to know about this concept because of the snag I experienced.

Let’s dive in!

To put things in context, the “aha” moment came while working on the Single Flight Pattern (SFP) implementation which I talked about in this writeup — you can read it up here (insert link here).

I needed to test the SFP implementation in isolation from the rest of the project, so I wrote the test script, and attempted running it from the command line:

node test-SFP.js
Enter fullscreen mode Exit fullscreen mode

Instead of seeing my test pass, I got the error in the command line shown in the screenshot below (emphasis on the lines with a yellow pointer/arrow);

COmmand Line

At first, I assumed I had broken something in the main SFP code I had written.

I hadn’t.

My First Assumption

Since my OAuth client relied on environment variables, I naturally suspected that the variable was not spelt correctly, so I deliberately deleted and rewrote that and retried running the script again, but got same result. I even went ahead to log other . env variables to be sure I could isolate the issue, but each time, I got an undefined printed in the console.

I also at some point begin to change the order of imports in my code to be sure I was not improperly importing related files.

At that point, I even started wondering whether I had misunderstood how ES Modules loaded dependencies.

Looking back, I was making a classic debugging mistake.

I was changing code before I had enough information about the problem.

Time to Stop Guessing

Instead of continuing to make changes, I decided to gather evidence.

The first thing I did was to log dotenv.config() (note that I imported this module into the test file because I was testing the SFP part of my application in isolation).

import dotenv from "dotenv";

const result = dotenv.config();
console.log(result);
Enter fullscreen mode Exit fullscreen mode

The output immediately changed the direction of my investigation.

ENOENT: no such file or directory
Enter fullscreen mode Exit fullscreen mode

See full output of the error / output in the screenshot attached below:

Command Line

The output in the screenshot was an important discovery for me!

It said there was no .env , yet I was sure it existed — in the project root!

So why was .dotenv insisting that it didn't?

The Clue Was Hidden in the Error Message

Reading the error more carefully, I noticed something I had completely overlooked.

The error wasn’t simply saying that .env couldn't be found.

It was telling me where it was looking.

C:\..\..\bot\src\services\.env
Enter fullscreen mode Exit fullscreen mode

My actual .env file wasn't there.

It lived here instead.

C:\..\..\bot\.env
Enter fullscreen mode Exit fullscreen mode

That completely changed the problem.

The issue was no longer:

dotenv can't find my .env file."

The issue became:

“Why is it looking inside src/services in the first place?"

That question eventually led me to the real lesson.

The Discovery

This debugging session introduced me to a Node.js concept that, despite using Node for quite some time, I had never really stopped to think about.

The Current Working Directory (CWD).

Up until this point, I had assumed that because I was executing test-SFP.js, any library — in this case dotenv that needed to find a file would automatically look relative to test-SFP.js.

That wasn’t the case.

When I called:

import dotenv from "dotenv";

dotenv.config();
Enter fullscreen mode Exit fullscreen mode

I wasn’t telling dotenv where my .env file lived.

So dotenv had to make a choice.

By default, it asked Node for the application’s Current Working Directory using:

process.cwd()
Enter fullscreen mode Exit fullscreen mode

and then looked for a .env file there.

This made me curious, so I printed the Current Working Directory.

console.log(process.cwd());
Enter fullscreen mode Exit fullscreen mode

When I executed my test like this:

cd src/services
node test-SFP.js
Enter fullscreen mode Exit fullscreen mode

the output was:

.../bot/src/services
Enter fullscreen mode Exit fullscreen mode

That meant dotenv naturally searched for:

bot/
└── src/
    └── services/
        └── .env
Enter fullscreen mode Exit fullscreen mode

But my actual project looked like this:

bot/
│
├── .env
├── package.json
└── src/
    └── services/
        └── test-SFP.js
        └── FlutterwaveClient.js

Enter fullscreen mode Exit fullscreen mode

In other words, dotenv wasn't failing to read my *.env * file.

It was simply looking in a different location than I expected.

Once that clicked, everything else started making sense.

Two Ways to Fix It

Once I understood the problem, I realized there were two perfectly valid solutions.

Option 1: Run the application from the project root

Instead of running the test script from src/services like I had been doing, I naviagted to the project root - bot and executed:

cd bot
node src/services/test-SFP.js
Enter fullscreen mode Exit fullscreen mode

Now the Current Working Directory became:

bot/
Enter fullscreen mode Exit fullscreen mode

which is exactly where my .env file lived.

Without changing a single line of code, dotenv.config() worked exactly as intended.

Option 2: Tell dotenv exactly where the file is

Alternatively, dotenv allows you to override its default behavior by specifying the location of the .env file explicitly.

import dotenv from "dotenv";

dotenv.config({
    path: "../../.env"
});
Enter fullscreen mode Exit fullscreen mode

When I tried this, I ran my script from inside src/services.

cd src/services
node test-SFP.js
Enter fullscreen mode Exit fullscreen mode

The Current Working Directory remained:

bot/src/services
Enter fullscreen mode Exit fullscreen mode

But this time, dotenv ignored its default lookup location because I had explicitly told it where to find the configuration file.

Both approaches solved the problem.

The important lesson was understanding why the default behavior had failed in the first place.

Once I understood that dotenv.config() uses the application's Current Working Directory by default, both solutions felt perfectly logical instead of feeling like random fixes.

Why This Makes Sense

After understanding the behavior, I actually found it quite reasonable.

Project-wide configuration files such as:

  • dotenv
  • package.json
  • tsconfig.json etc typically live in the project’s root directory.

It makes sense for libraries like dotenv and the others mentioned to assume that the application starts from the project root.

That way, every file in the project shares the same configuration regardless of where the file itself lives.

Lessons For The Future

This experience left me with a few lessons that probably will remember for a long time.

1. A script’s location is not the same as the application’s Current Working Directory.

Those are two different concepts, and they affect how many libraries resolve files.

2. Error messages often tell you more than you initially realize.

The error wasn’t just saying that .env couldn't be found.

It was telling me exactly where it had looked.

I simply hadn’t paid enough attention to that detail.

3. Stop guessing. Start gathering evidence.

The turning point wasn’t another code change.

It was inspecting the output of dotenv.config() and allowing the error message to guide the investigation.

4. Understanding the runtime is just as important as writing code.

This wasn’t really a *dotenv * problem.

It was a Node.js concept that I hadn’t fully understood yet.

Top comments (0)