• About
  • Articles
  • Projects
  • Uses
AboutProjectsArticlesPrivacySubscription Terms

© 2026 Carlos Zaragoza. All rights reserved.

CI Was Green. The Build Wasn’t.

August 24, 2026·5 min read

My CI pipeline passed while Next.js was quietly trying to access DynamoDB during the build. Instead of giving CI AWS credentials, I followed the warning and found an architectural problem hiding behind a green check mark.


My CI pipeline was green. That should have been the end of it. Tests passed. TypeScript passed. Next.js finished building. GitHub gave me the check mark I wanted.

But buried in the build output was this:

text
Failed to list published articles.
 
CredentialsProviderError:
Could not load credentials from any providers

A few lines later:

text
✓ Next.js production build passed

Technically, CI was telling the truth. The build passed. But something was wrong.

The warning mattered more than the check mark#

The error came from my article repository:

text
listPublished
    ↓
DynamoDB
    ↓
CredentialsProviderError

GitHub Actions didn't have AWS credentials. That was intentional. My first reaction could have been:

CI needs access to DynamoDB. I'll give it credentials.

That would have made the error disappear. It also would have solved the wrong problem. The better question was:

Why is my production build trying to access DynamoDB at all?

CI was compiling and validating my application. It wasn't serving a request from a user.

There should have been no reason for next build to reach into my production infrastructure.

Don't fix an architectural problem with more permissions#

Giving CI AWS credentials would have been easy.

A role, a few permissions, maybe:

text
dynamodb:GetItem
dynamodb:Query

and the warning probably disappears. But now the CI environment has another credential path to maintain. Another role to secure. Another policy to audit. Another place capable of reaching infrastructure.

And for what?

To support a database call that shouldn't have happened during the build in the first place. That's a bad trade.

Credentials should exist because a system needs them, not because they're convenient enough to make an error go away.

Following the stack#

The useful part of the error was the stack trace.

It pointed through:

text

That made the problem much easier to reason about. The DynamoDB client wasn't randomly connecting to AWS.

Some application code was being evaluated during the build, and that code eventually crossed the persistence boundary.

At the time, I still had public article API routes such as:

text
/api/article
/api/article/[slug]

But my public Server Components no longer needed them.

The pages already had a direct server-side path:

text

The public HTTP routes were effectively dead architecture. Nothing in the application needed to call them. Yet they still existed, and Next.js was touching enough of that surface during the production build to expose the unwanted dependency.

So I removed them.

The real fix was less architecture#

That was probably my favorite part of the solution.

I didn't add:

text
CI
 ↓
AWS credentials
 ↓
IAM role
 ↓
DynamoDB

I deleted a path.

The architecture became smaller:

text
Public page
    ↓
server query
    ↓
application service
    ↓
repository

while the HTTP layer remained where it was actually needed:

text
Admin client
    ↓
authenticated API
    ↓
application service
    ↓
repository

No unused public API. No build-time database access. No AWS credentials in CI. The warning went away because the thing causing it no longer existed.

Reproducing CI locally#

I wanted to make sure the fix was real.

My development machine normally had valid AWS credentials through SSO, so simply running the build locally wasn't a meaningful test. A hidden DynamoDB request could succeed locally and still fail in GitHub Actions. So I logged out of AWS SSO.

Then I verified that my machine could no longer authenticate to AWS. Only after that did I run the same checks again. The question wasn't merely:

Does the build pass?

It was:

Does the build pass in an environment that cannot access AWS?

It did. And this time the logs were clean. That was the result I actually wanted.

A green pipeline is a signal, not a diagnosis#

CI systems ultimately care a lot about exit codes. If a process encounters an error, handles it, and eventually exits successfully, the pipeline can still be green. That's perfectly reasonable behavior.

But it means this:

text
✓ Build passed

doesn't necessarily mean:

text
Nothing suspicious happened.

In my case, the application tolerated the DynamoDB failure well enough for Next.js to complete the build. That made the pipeline technically successful. It didn't make the behavior correct.

The Ferrari still runs#

I think about it like a Ferrari with a missing air filter. You turn the key. The engine starts. You drive it around the block.

It's fast. You could look at that and conclude:

The car works.

And technically, you'd be right.

But if you notice that missing air filter, you don't ignore it because the car still moves. The fact that the system can continue operating doesn't make every warning irrelevant.

CI is the same way. A green check mark tells me that the process completed successfully.

The logs tell me how it got there. Both matter.

What I took away from it#

The biggest lesson wasn't about DynamoDB or Next.js. It was about resisting the easiest fix. When CI complained that it couldn't access AWS, granting CI access would have solved the immediate symptom.

Instead, asking why it wanted access at all exposed unnecessary application architecture.

The final system ended up with:

  • fewer routes,
  • fewer dependencies during the build,
  • no AWS credentials in CI,
  • a clearer server-side data path,
  • and cleaner build output.

All because I didn't ignore an error inside a successful build. A green pipeline is useful.

A green pipeline with clean logs is better.

next build ↓ /api/article ↓ listPublishedArticlesService ↓ articleRepository.listPublished() ↓ DynamoDB
Server Component ↓ server query ↓ application service ↓ repository ↓ DynamoDB