CodeBuild "No Matching Artifact Paths Found": Fix the cd Trap, base-directory, discard-paths, Secondary Artifacts and Every UPLOAD_ARTIFACTS Error
If CodeBuild fails with CLIENT_ERROR: no matching artifact paths found, every pattern under artifacts: files: matched zero files. CodeBuild resolves those patterns from the original build location ($CODEBUILD_SRC_DIR, the root of your repository) or from base-directory if you set one. It never resolves them from the folder your commands moved into with cd. So if your build ran cd web && npm run build, the output is at web/dist, and dist/**/* finds nothing. Write web/dist/**/*, or better, set base-directory: web/dist with files: '**/*'.
Jake's shop website had built cleanly for a year. Then Ethan moved it into a web/ folder so the shop's small stock-check API could live in the same repository, and the next build went red at the very last step. "The build passed," Jake said, pointing at the green BUILD line. "Everything passed. Then it says nothing matched." Ethan scrolled up to one line in the log, Expanding dist/**/*, and laughed. "It's looking for the files in the old place. The files are fine. The address is wrong."
New to this? AWS CodeBuild is AWS's build service: it starts a fresh container, copies in your source code, runs the commands in your buildspec.yml file and then throws the container away. Anything you want to keep, such as a compiled website, a .jar file or a deployment bundle, has to be named in the buildspec's artifacts section. CodeBuild collects those files, zips them and uploads them to an Amazon S3 bucket, or hands them to the next stage of a CodePipeline. If the list of files comes back empty, the build fails, because a build that was supposed to produce something and produced nothing is almost always a mistake.
What "no matching artifact paths found" actually means
CodeBuild works through a fixed list of phases, and the artifacts step is near the very end. That is why this error feels so unfair: every line of your own code ran and passed, and the build still fails. Here is the tail of a real failing build, run through the CodeBuild agent with a buildspec that did cd app before building:
Running command pwd
/codebuild/output/src1659836322/src/app
Phase complete: BUILD State: SUCCEEDED
Entering phase POST_BUILD
Phase complete: POST_BUILD State: SUCCEEDED
Expanding base directory path: .
Assembling file list
Expanding .
Expanding file paths for base directory .
Assembling file list
Expanding dist/**/*
Phase complete: UPLOAD_ARTIFACTS State: FAILED
Phase context status code: CLIENT_ERROR Message: no matching artifact paths found
Read it from the bottom up. CLIENT_ERROR means the problem is in your configuration, not inside AWS. Expanding dist/**/* is the exact pattern CodeBuild tried. Expanding base directory path: . means no base-directory was set, so the pattern started at ., the original build location. And the pwd line a few lines higher shows the shell was sitting in .../src/app when the commands ended. The files existed. They were in app/dist, one folder deeper than where CodeBuild looked.
When the patterns do match, the same block ends differently, and the count tells you exactly how many files were collected:
Expanding base directory path: app/dist
Assembling file list
Expanding app/dist
Expanding file paths for base directory app/dist
Assembling file list
Expanding **/*
Found 2 file(s)
Preparing to copy secondary artifacts
No secondary artifacts defined in buildspec
Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED
Ethan: "Think of it as a delivery driver with a list of addresses. The driver doesn't care which room you were standing in when you wrote the list. Every address starts from the front door."
Jake: "And I wrote 'the box on the left' while I was standing in the back room."
Where this step sits among the build phases matters, because it tells you what has already happened by the time the error appears:
| Phase | What happens | Artifact problems that show up here |
|---|---|---|
| DOWNLOAD_SOURCE | Source copied in, buildspec parsed | YAML errors in the artifacts block, such as an unquoted **/* |
| INSTALL, PRE_BUILD, BUILD, POST_BUILD | Your commands run, in one shell | Files written to the wrong folder, or never written because a command failed |
| UPLOAD_ARTIFACTS | Patterns expanded, files zipped, upload to S3 or CodePipeline | no matching artifact paths found, no matching base directory path found, AccessDenied, Invalid arn |
| FINALIZING, COMPLETED | Logs flushed, container removed | Nothing new; anything not uploaded is gone |
Find where your files really are in 30 seconds
Guessing paths is how a ten-minute fix turns into an afternoon. Ask the build container instead. Add these lines as the last commands in post_build, run the build once, and read the answers in the build log:
post_build:
commands:
- echo "Repository root is $CODEBUILD_SRC_DIR"
- echo "Shell is now in $(pwd)"
- find "$CODEBUILD_SRC_DIR" -maxdepth 3 -type d \( -name dist -o -name build -o -name out -o -name target -o -name public \) -not -path '*/node_modules/*'
- ls -la "$CODEBUILD_SRC_DIR"
- Compare the two echo lines. If
pwdis not the repository root, some command moved the shell, and every artifact path must include that extra folder (or you setbase-directoryto it). - Read the
findoutput. It prints the real output folder, for example/codebuild/output/src123/src/web/dist. Remove the repository root from the front, and what is left (web/dist) is the path you write in the buildspec. - Check the spelling and case. The build container runs Linux, where
Distanddistare different folders. - If
findprints nothing, the build never wrote the folder. Scroll up and look for a command that failed, or a build tool that wrote somewhere else, such as/tmpor your home folder. - Update the
artifactsblock with the path from step 2, push, and look forFound N file(s)in the next log.
The repository root is a different random folder in every build (src1659836322 above), so never paste an absolute path from the log into the buildspec. Always use the part after .../src/.
The cd trap: why artifacts ignore the folder you moved into
In buildspec version 0.2, CodeBuild runs every command in the same shell, one after another. That is a feature: an export or a cd in one command is still in effect for the next. Version 0.1 started a fresh shell for each command, which surprised people the other way. So this works exactly as you would expect at a terminal:
version: 0.2
phases:
build:
commands:
- cd web
- npm ci
- npm run build # writes web/dist
artifacts:
files:
- 'dist/**/*' # wrong: looks for dist at the repository root
The commands all ran inside web, because the shell stayed there. But the artifacts section is not a command. It runs after your shell has finished, and the buildspec reference is specific: the locations are relative to the original build location or, if set, the base directory. Your cd has no effect on it. Running cd "$CODEBUILD_SRC_DIR" at the end of post_build does not help either, for the same reason: the artifacts step never looked at where the shell was.
You can see both halves of this in one build. Here the BUILD phase wrote app/dist and then ran cd app. The next phase, POST_BUILD, ran pwd, and the artifacts block asked for app/dist/**/*, written from the root:
Running command cd app
Phase complete: BUILD State: SUCCEEDED
Entering phase POST_BUILD
Running command pwd
/codebuild/output/src3659113198/src/app
Phase complete: POST_BUILD State: SUCCEEDED
Expanding base directory path: .
Expanding app/dist/**/*
Found 2 file(s)
Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED
The cd carried over from one phase into the next: the shell was still inside app. And the artifacts step ignored it completely and found the files from the root. Same shell for your commands, fixed starting point for the artifacts. Once you know those two rules, the error stops being mysterious.
There are two correct fixes. Both work; the second gives you a cleaner zip.
# Fix 1: write the full path from the repository root
artifacts:
files:
- 'web/dist/**/*'
# Fix 2: start from the output folder
artifacts:
base-directory: web/dist
files:
- '**/*'
The difference shows up inside the zip. With fix 1, CodeBuild keeps the path you matched, so the zip contains web/dist/index.html. With fix 2, the base directory itself is left out, so the zip contains index.html at the top. If the next step copies the zip's contents to an S3 website bucket, fix 1 puts your homepage at /web/dist/index.html and the site's root URL returns an error. That second failure is covered further down, because it looks like a completely different problem.
Ethan: "The build log even told us. It said 'Expanding dist' while the shell was standing in web."
Jake: "In fairness to me, it said it in a very quiet voice."
How files, base-directory and discard-paths decide what gets packaged
Three keys control almost everything about the primary artifact. Here is what each one does, in plain terms:
files(required): one or more patterns.*matches any name inside one folder;**/*matches every file in every folder below. Patterns are relative to the repository root, or tobase-directorywhen it is set. You do not need./or the repository's absolute path.base-directory(optional): the folder the patterns start from. The folder itself is not included in the zip, only what is inside it. It can be a pattern too:'build*'matches every top-level folder whose name starts withbuild.discard-paths(optional,yesorno, defaultno): withyes, every file lands in the top of the zip and all folder structure is thrown away.
These are the zips the CodeBuild agent produced for the same small website, built into app/dist with an index.html and an assets/app.js:
| Artifacts block | Log result | What the zip contained |
|---|---|---|
files: app/dist/**/* | Found 2 file(s) | app/dist/index.html, app/dist/assets/app.js |
files: ./app/dist/**/* | Found 2 file(s) | Same as above; the ./ is accepted and dropped |
base-directory: app/dist + files: '**/*' | Found 2 file(s) | index.html, assets/app.js (folder left out) |
files: app/dist/* | Found 1 file(s) | app/dist/index.html only; assets/ skipped |
files: app/dist (a folder, no wildcard) | FAILED | no matching artifact paths found |
files: app/Dist/**/* (wrong case) | FAILED | no matching artifact paths found |
base-directory: app/dist + '**/*' + discard-paths: yes | Found 4 file(s) | Everything at the top, including two entries both named config.json |
Two rules are worth memorizing. A pattern that names a file directly (appspec.yml) picks up one file. A pattern that should pick up a folder's contents needs a wildcard (app/dist/**/*), and **/* is the one you almost always want.
Which folder does your build tool write to?
Half of all "no matching artifact paths" errors come from writing the folder name you expected rather than the one your tool uses. These are the default output folders for common tools. A setting in your project, such as Vite's build.outDir or a Maven plugin, can change them, so when in doubt, use the find command above.
| Tool or framework | Default output | Artifacts block |
|---|---|---|
| Vite (React, Vue, Svelte templates), Astro, Vue CLI | dist/ | base-directory: dist, files: '**/*' |
| Create React App, Docusaurus, SvelteKit with adapter-static | build/ | base-directory: build |
| Angular 17 and later (application builder) | dist/<project-name>/browser/ | base-directory: dist/shop-web/browser |
Next.js with output: 'export' | out/ | base-directory: out |
Nuxt 3 nuxi generate | .output/public/ | base-directory: .output/public |
| Gatsby, Hugo | public/ | base-directory: public |
| Jekyll | _site/ | base-directory: _site |
| Maven | target/*.jar or .war | files: target/*.jar |
| Gradle | build/libs/*.jar | files: build/libs/*.jar |
.NET dotnet publish | Varies by version and runtime; pass -o publish | base-directory: publish |
| Go | Wherever go build -o says | go build -o bin/app, then files: bin/app |
Two rows deserve a warning. Angular's folder includes your project's name, so renaming the project in angular.json quietly moves the output. And Nuxt writes to a folder whose name starts with a dot, which is the one place hidden-folder behavior (covered below) suddenly matters for the base directory itself.
For .NET, Go and anything else where you control the output folder, choose a name once in the command (-o publish) and use the same word in the artifacts block. A path you wrote yourself in two places is a path you can check by reading.
Every cause of the error, with the fix
If the find check above did not make the answer obvious, work down this list. Each cause comes with the log line or symptom that identifies it.
A cd moved the shell
Symptom: pwd in post_build prints a subfolder, and the pattern in the Expanding line is missing that folder. Fix: add the folder to every path, or use base-directory. Details in the cd trap above.
The output folder has a different name
Symptom: find shows build/ where you wrote dist/, or dist/shop-web/browser where you wrote dist. Fix: use the real name from the output folder table. If you changed frameworks recently, this is almost certainly it.
One letter is the wrong case
Symptom: the path looks right, and it works on your Mac or Windows laptop. files: app/Dist/**/* against a real app/dist folder fails with the same no matching artifact paths found, because Linux treats Dist and dist as two different names. Laptops usually do not, which is why it "works locally". Fix: copy the folder name from the find output rather than typing it.
A folder is listed without a wildcard
Symptom: Expanding app/dist followed by the failure. Naming a folder does not mean "everything in it": files: app/dist matches no files and fails. Fix: app/dist/**/*.
A single star skipped the subfolders
Symptom: no error at all, but the site loads without its scripts or images. app/dist/* matches only the files directly inside dist, so it collected index.html and quietly skipped assets/app.js (Found 1 file(s)). Fix: two stars, app/dist/**/*, which collected both.
base-directory points at a folder that does not exist
Symptom: a slightly different message, which is a gift because it tells you exactly which setting is wrong: CLIENT_ERROR Message: no matching base directory path found for build. The word at the end is not a figure of speech: it is the folder you set. This came from base-directory: build on a project that writes dist, and the log line above it reads Skipping invalid file path build. CodeBuild never gets as far as the files patterns. Fix: correct the base directory. Remember that it is also measured from the repository root, so a project in web/ needs base-directory: web/dist, not dist.
The build wrote outside the repository
Symptom: your tool writes to /tmp, your home folder or a path like /opt/build. The surprise here is that an absolute pattern does match: files: /tmp/out/* reported Found 1 file(s) and succeeded. But look at what went into the zip:
$ unzip -o artifacts.zip
Archive: artifacts.zip
warning: stripped absolute path spec from /tmp/out/x.txt
inflating: tmp/out/x.txt
The entry was stored as /tmp/out/x.txt, with the leading slash, and unzip had to strip it and recreate a tmp/out folder. Every tool downstream sees a different layout from the one you meant. Fix: make the tool write inside the repository (most have an output flag), or copy the result in before the artifacts step: cp -r /tmp/out "$CODEBUILD_SRC_DIR/out", then base-directory: out.
An earlier command failed
Symptom: a COMMAND_EXECUTION_ERROR earlier in the log, then this error. The build never got far enough to write the output folder. Fix: fix the first error. The artifacts error is just the echo. See the next section for why the artifacts step runs at all after a failure.
The file was never committed
Symptom: a file you can see on your own machine is missing from the build, often imagedefinitions.json, a prebuilt dist folder, or a generated config. CodeBuild only has what is in your source, and a file listed in .gitignore never reaches it. Fix: generate the file during the build, or commit it.
Something deleted the output
Symptom: find shows the folder in the BUILD phase but not at the end of POST_BUILD. A cleanup step such as rm -rf dist, git clean -fdx or a tool's own "clean" target ran after the build. Fix: move cleanup before the build, or out of the buildspec entirely; the container is thrown away anyway.
CodeBuild is reading a different buildspec
Symptom: your fix to buildspec.yml changes nothing, and the Expanding line still shows the old pattern. The project may use an inline buildspec entered in the console, a different file name such as buildspec-prod.yml, or the pipeline may pass a buildspec override. Fix: check the project's Buildspec setting, and the YAML location line near the top of the log, which names the file actually used.
An unquoted star breaks the whole file
Symptom: not this error, but a YAML_FILE_ERROR before any command runs. In YAML, a value that starts with * is read as a reference to an anchor, so this:
artifacts:
files:
- **/*
fails in DOWNLOAD_SOURCE with YAML_FILE_ERROR Message: did not find expected alphabetic or numeric character at line 7. In that eight-line buildspec, line 7 is files: and the broken - **/* is on line 8, so look at the line after the one the message names, too. Fix: quote it, - '**/*'. Patterns that start with a letter (app/dist/**/*) are safe without quotes, but quoting every pattern is a good habit. More YAML traps are in CodeBuild YAML_FILE_ERROR.
One of several files is missing, and nothing complains
This one is worse than an error, because there isn't one. With two patterns, one matching and one not:
artifacts:
files:
- app/dist/**/*
- appspec.yml # this file does not exist
the log shows a single quiet line, and the phase succeeds:
Expanding app/dist/**/*
Expanding appspec.yml
Skipping invalid file path appspec.yml
Found 2 file(s)
Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED
no matching artifact paths found appears only when every pattern comes back empty. A missing appspec.yml or imagedefinitions.json sails through the build and breaks the deploy stage instead, where the error message mentions the deployment, not the buildspec. Fix: search the build log for Skipping invalid file path, and add a test -f check for every file the next stage depends on.
Jake: "So the loud error is the friendly one."
Ethan: "The loud one stops you. The quiet one lets you ship a box with a missing leg."
Hidden files: what '**/*' packs that you did not expect
This is the part of the artifacts section that has nothing to do with the error, and it is the part most worth reading. '**/*' means every file, and that includes files and folders whose names start with a dot. The build below wrote a normal site into app/dist, plus a .env file and a .cache folder, the kind of thing a framework or a careless script leaves behind:
artifacts:
base-directory: app/dist
files:
- '**/*'
Found 4 file(s)
$ unzip -l artifacts.zip
9 .env
14 index.html
15 assets/app.js
2 .cache/x
The .env went straight into the artifact. If that artifact is a website copied to a public S3 bucket, the file is now one URL away from anyone who guesses /.env, which is one of the first paths automated scanners try. The empty folder the build also created, empty/, was not in the zip at all: artifacts hold files, so a folder with nothing in it disappears.
The fix is exclude-paths, which takes patterns relative to base-directory. These two lines are the ones the buildspec reference gives for dot files and dot folders:
artifacts:
base-directory: app/dist
files:
- '**/*'
exclude-paths:
- '**/.*' # dot files anywhere
- '**/.*/**/*' # everything inside dot folders
Found 2 file(s) # index.html and assets/app.js only
Do the same for source maps ('**/*.map') if you do not want to publish your original source, and for anything like *.pem, *.key or config.local.* that might appear in a build folder. Better still, keep secrets out of the build folder in the first place: load them at build time from Parameter Store or Secrets Manager, as covered in the buildspec guide.
One caution for Nuxt and other tools whose output folder itself starts with a dot (.output/public): put that folder in base-directory, and the exclude-paths patterns, which are relative to it, still only remove dot files inside the site.
Ethan: "Every shop has a back room. The artifact is the shop window. Don't put the back room in the window."
discard-paths: yes and two files with the same name
discard-paths: yes looks harmless: it just flattens the folders. The danger is what happens when two folders hold files with the same name. The build in the table above wrote app/dist/a/config.json (containing A) and app/dist/b/config.json (containing B). CodeBuild reported Found 4 file(s), the phase succeeded, and the zip it produced really does contain two separate entries with the identical name config.json:
$ python3 -c "import zipfile; z=zipfile.ZipFile('artifacts.zip'); [print(i.filename, z.read(i).decode().strip()) for i in z.infolist()]"
index.html <h1>shop</h1>
app.js console.log(1)
config.json A
config.json B
$ unzip -o -q artifacts.zip && cat config.json
B
No warning anywhere. Whatever unpacks the zip next keeps one of them, and with unzip -o and Python's zipfile it was the last one, B, so folder a's settings silently vanished from the deployed result. Picture that with two index.html files from a site that has a /blog/index.html and a home page: your homepage could be replaced by the blog index, and the build log would be green.
- Use
discard-paths: yesonly when you know every file name is unique, such as a handful of.jarfiles or reports. - For websites and anything with nested folders, keep the default (
no) and usebase-directoryto drop the leading folders instead. - If you need a few specific files flattened, list them individually rather than using
**/*.
Ethan: "Flattening a folder tree is like emptying every drawer in the shop onto one table."
Jake: "And the two price lists with the same name? One of them ends up under the other."
Where the artifact lands in S3: name, path, namespace and packaging
When the build project's artifact type is S3, four settings in the project, plus an optional name in the buildspec, decide the object key. This is the part people get wrong when a build "succeeds" but nobody can find the zip:
| path | namespaceType | name | Where it lands in the bucket |
|---|---|---|---|
| MyArtifacts | NONE | MyArtifact.zip | MyArtifacts/MyArtifact.zip |
| MyArtifacts | BUILD_ID | MyArtifact.zip | MyArtifacts/<build-ID>/MyArtifact.zip |
| (empty) | NONE | / | The root of the bucket |
| MyArtifacts | BUILD_ID | / | MyArtifacts/<build-ID> |
packaging decides the shape: NONE (the default) uploads the files as a folder of separate objects, and ZIP uploads one zip file. If someone opens the bucket expecting one .zip and finds a folder of loose files instead, packaging is still NONE. Neither is wrong: NONE suits a website you copy file by file, and ZIP suits a bundle you deploy or archive as one piece.
The buildspec name only takes effect when the project allows it: overrideArtifactName set to true in the API, or Enable semantic versioning selected in the console. With that on, the name is evaluated as a shell expression at build time, so name: shop-web-$(date +%Y-%m-%d) or name: builds/$CODEBUILD_BUILD_NUMBER/shop-web gives every build its own object. Without a unique name, each build overwrites the previous artifact, which is fine for "latest" and terrible for rollbacks.
Two smaller settings explain the rest of the surprises. s3-prefix in the buildspec puts a prefix in front when the namespace type is BUILD_ID, giving <s3-prefix>/<build-id>/<name>.zip. And encryptionDisabled is only valid for the S3 type; set it on any other artifact type and the API rejects the project with an InvalidInputException.
When the type is CODEPIPELINE, CodePipeline ignores the location, name, path, namespace and packaging settings, because it manages its own artifact bucket. Teams switching a project from standalone to pipeline often go looking for their zip in the old bucket. It is in the pipeline's artifact bucket now, under a folder named after the pipeline.
Inside CodePipeline: output artifacts, secondary artifacts and PrimarySource
A CodeBuild action in CodePipeline takes 1 to 5 input artifacts and produces 0 to 5 output artifacts. The rules that trip people up:
- One output artifact comes straight from the top-level
artifactsblock. Whatever name you give it in the pipeline (BuildOutput, say) does not appear in the buildspec. - Two or more output artifacts must all be defined under
secondary-artifacts, and each pipeline output artifact name must match an artifact identifier in the buildspec exactly. - The top-level
fileslist is still required, even when you only use secondary artifacts. Leave it out and the buildspec is invalid. - Several input artifacts need
PrimarySourceset in the action. CodeBuild reads the buildspec from the primary source and runs your commands there ($CODEBUILD_SRC_DIR). Every other input is unzipped into its own folder, available as$CODEBUILD_SRC_DIR_<InputArtifactName>.
version: 0.2
phases:
build:
commands:
- cd web && npm ci && npm run build && cd ..
- cd api && zip -qr ../api.zip . && cd ..
artifacts:
files:
- '**/*'
base-directory: web/dist
secondary-artifacts:
WebSite: # must match the pipeline output artifact name
base-directory: web/dist
files:
- '**/*'
ApiBundle:
files:
- api.zip
- api/appspec.yml
If you test this with the CodeBuild agent on your own machine, each secondary artifact comes out as its own zip next to the primary one, named after its identifier (artifacts-WebSite.zip beside artifacts.zip), and the log repeats the Expanding ... and Found N file(s) block once per artifact after a Preparing to copy secondary artifacts line. That makes it easy to check each identifier's contents before a pipeline ever runs.
Secondary artifacts also change how you read the error. Here the primary artifact matched fine, and a secondary artifact named ApiBundle asked for an api.zip that the build never created:
Expanding **/*
Found 2 file(s)
Preparing to copy secondary artifacts
Expanding base directory path: .
Expanding api.zip
Skipping invalid file path api.zip
Phase complete: UPLOAD_ARTIFACTS State: FAILED
Phase context status code: CLIENT_ERROR Message: no matching artifact paths found
The message is word for word the same as for the primary artifact, and nothing in it says "secondary". The clue is position: if the failure comes after Preparing to copy secondary artifacts, the primary block is fine and one of the secondary blocks is the one matching nothing. Each secondary artifact needs at least one file, and unlike a missing file in the primary list, an empty secondary artifact fails the whole phase.
Artifact names in CodePipeline can be up to 100 characters of letters, numbers, hyphens and underscores, with no spaces, so pick boring names like WebSite and ApiBundle and copy them, never retype them. A secondary artifact identifier that differs by one letter from the pipeline's output artifact name fails exactly like a wrong path does: nothing ever arrives under the name the next stage is waiting for.
Ethan: "The pipeline is the delivery van. The buildspec writes the labels on the boxes. If the van is waiting for a box called WebSite and you labeled it Website, it drives off empty."
The next stage can't find a file: deploy-side symptoms of a bad artifact
Sometimes the artifacts step succeeds, but it packs the wrong thing, and the error appears one stage later where nobody thinks to look at the buildspec. These are the common ones:
- S3 deploy action, website returns 403 or 404 at the root. With Extract file before deploy on, the S3 deploy action unpacks the zip into the bucket exactly as it is structured. Pack
web/dist/**/*without a base directory and your homepage lands atweb/dist/index.htmlin the bucket. Usebase-directory: web/dist. If the root still errors after that, see S3 static website 403 fixed. - Amazon ECS standard deploy. The ECS deploy action reads an
imagedefinitions.jsonfile (container name and image URI, UTF-8, as a JSON array) from its input artifact. The build has to write it, typically withprintf '[{"name":"web","imageUri":"%s"}]' "$IMAGE_URI" > imagedefinitions.jsoninpost_build, and the artifacts block has to include it:files: imagedefinitions.json. A file that exists in the container but is missing fromfilesnever reaches ECS. - ECS blue/green through CodeDeploy (CodeDeployToECS). It needs the task definition file and an AppSpec in its input artifacts, and that artifact zip must be under 3 MB. Over the limit, or with the file missing, it fails with
Exception while trying to read the task definition artifact file from: <source artifact name>. Pack only the two small files into a separate secondary artifact rather than shipping them inside a large build output. - Elastic Beanstalk deploys a WAR, then returns 404. Beanstalk can unpack a zip, but not a WAR inside a zip. Point the artifact at the exploded web-app folder instead of the
.warfile. - CloudFormation deploy fails on size. Its input artifacts are limited to 256 MB, and Beanstalk deploys to 512 MB. Source artifacts can be up to 7 GB from S3 and 1 GB from CodeCommit or GitHub.
- Lambda deploys and then fails on size. Large dependency folders swept up by
**/*can push the package past Lambda's limits. The fixes are in Lambda "unzipped size must be smaller".
The habit that prevents all of these is the same: unzip the artifact once and look at it. In a pipeline, the artifact is in the pipeline's S3 artifact bucket; download it and run unzip -l before you change anything else.
When the paths match but the upload still fails
If the log shows Found N file(s) and UPLOAD_ARTIFACTS still fails, the files are fine and the trip to S3 is the problem:
| Error | Cause | Fix |
|---|---|---|
Failed to upload artifacts: Invalid arn | The output bucket is in a different Region from the build project | Use an output bucket in the project's Region |
| AccessDenied on upload | The service role lacks s3:PutObject on the bucket, or a bucket policy denies it | Allow s3:PutObject on arn:aws:s3:::bucket/*; check the bucket policy for Deny statements |
| AccessDenied with a KMS mention | The bucket uses SSE-KMS and the role cannot use the key | Allow kms:GenerateDataKey (and kms:Decrypt for downloads) in the key policy; see S3 and KMS AccessDenied |
Error calling GetBucketAcl: Either the bucket owner has changed... | The role has s3:GetBucketAcl and s3:GetBucketLocation, and the bucket changed owner | Confirm you own the bucket, then re-add the permissions |
Pipeline InternalError with JobFailed | The pipeline's artifact bucket is in another Region | One artifact bucket per Region, in the pipeline's account |
| Objects uploaded, but the bucket owner cannot read them | Cross-account bucket; the uploading account owns the objects | Set bucketOwnerAccess to READ_ONLY or FULL (needs s3:PutBucketAcl) |
The minimum S3 statement a CodeBuild service role needs to upload artifacts is small. Scope it to your bucket rather than the "Resource": "*" you see in starter policies:
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:GetObjectVersion",
"s3:GetBucketAcl", "s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::shop-build-artifacts",
"arn:aws:s3:::shop-build-artifacts/*"]
}
A permissions problem never produces no matching artifact paths found. That message is printed before CodeBuild talks to S3 at all, which is useful: if you see it, stop looking at IAM.
A buildspec that fails loudly, and early
One behavior catches almost everyone, and it is the reason the checks below matter. When a command in the BUILD phase fails, CodeBuild does not skip straight to the end. It still runs POST_BUILD, and it still runs the artifacts step. Here is a build whose second command failed:
Running command echo compile error && exit 1
compile error
Command did not exit successfully echo compile error && exit 1 exit status 1
Phase complete: BUILD State: FAILED
Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: echo compile error && exit 1. Reason: exit status 1
Entering phase POST_BUILD
Phase complete: POST_BUILD State: SUCCEEDED
Expanding app/dist/**/*
Found 2 file(s)
Phase complete: UPLOAD_ARTIFACTS State: SUCCEEDED
The build as a whole failed, but the half-finished files that the first command wrote were collected and uploaded as if nothing had happened. Two consequences follow:
- A failed build can leave an artifact behind. If your artifact name never changes, that half-built zip replaces the last good one in S3. Anything that reads "the latest artifact" outside a pipeline can pick it up.
- A failed build can also produce this error as a second error. If the build failed before writing anything, the artifacts step then reports
no matching artifact paths foundtoo. Always fix the first failure in the log, the COMMAND_EXECUTION_ERROR, before touching the artifacts block. The exit codes are decoded in CodeBuild COMMAND_EXECUTION_ERROR.
A two-line check at the end of post_build turns the vague artifacts error into a message that names the missing file, and it costs nothing when the build is healthy:
version: 0.2
env:
shell: bash
phases:
install:
runtime-versions:
nodejs: 22
build:
commands:
- cd "$CODEBUILD_SRC_DIR/web"
- npm ci
- npm run build
post_build:
commands:
- cd "$CODEBUILD_SRC_DIR"
- test "$CODEBUILD_BUILD_SUCCEEDING" = 1 || { echo "Build failed earlier; skipping checks"; exit 1; }
- test -f web/dist/index.html || { echo "web/dist/index.html is missing"; ls -la web; exit 1; }
artifacts:
base-directory: web/dist
files:
- '**/*'
exclude-paths:
- '**/*.map'
CODEBUILD_BUILD_SUCCEEDING is set to 1 while the build is succeeding and 0 once something has failed, so the first test stops the checks from piling a second, misleading error on top of the real one. The cd "$CODEBUILD_SRC_DIR" line makes the paths in your checks match the paths in the artifacts block, which is the whole point.
Worked example: a monorepo website to S3 and CloudFront
This is the buildspec Jake's shop runs now. The repository has web/ (a Vite site) and api/ (a small service deployed separately). The build project writes artifacts to S3 with packaging NONE, and a final command copies the files to the website bucket and refreshes CloudFront:
version: 0.2
phases:
install:
runtime-versions:
nodejs: 22
build:
commands:
- cd "$CODEBUILD_SRC_DIR/web" && npm ci && npm run build
post_build:
commands:
- cd "$CODEBUILD_SRC_DIR"
- test -f web/dist/index.html
- aws s3 sync web/dist "s3://$SITE_BUCKET" --delete
- aws cloudfront create-invalidation --distribution-id "$CF_ID" --paths '/index.html'
artifacts:
base-directory: web/dist
files:
- '**/*'
name: shop-web-$CODEBUILD_BUILD_NUMBER
cache:
paths:
- 'web/node_modules/**/*'
Each piece earns its place. The cd into web is fine, because nothing after it relies on where the shell is: post_build moves back to the root on purpose. The test -f stops the deploy if the build produced no homepage. The artifact name includes the build number, so every build's output is kept and a rollback is one copy command away. And the cache path is written from the repository root too, because cache: paths follows the same rule as artifacts. If aws s3 sync leaves old files behind or deletes too much, the --delete details are in aws s3 sync not deleting files.
Jake: "So the buildspec reads like a map of the repo now."
Ethan: "Every path starts at the front door. That's the whole trick."
Check the artifacts block before you push
Every failed build costs a few minutes of waiting and a few cents of build time, and artifact mistakes always fail at the very end, after the slow part. Two quick ways to check first:
- Dry-run the patterns in your own terminal. From the repository root, after a local build, run
shopt -s globstar; ls web/dist/**/*in bash. If that lists your files, the same pattern will match in CodeBuild. If you usebase-directory,cdinto it first and test thefilespattern from there. - Run the real agent locally. AWS publishes the CodeBuild agent as a container image, and the
codebuild_build.shscript runs your buildspec through it with Docker. Pass an artifacts folder with-a, and the agent writesartifacts.zipthere;unzip -lshows exactly what CodeBuild would have uploaded. The setup is covered in CodeBuild YAML_FILE_ERROR and the buildspec. - Read the artifacts block aloud from the repository root. "From the root, go into web, then dist, take everything." If a sentence needs "after the build moved into", the path is wrong.
The local agent prints the same Expanding and Found N file(s) lines as the real service, so the two habits catch the same mistakes in seconds instead of minutes.
The two-minute checklist
- Find
Expanding ...in the log and read the pattern CodeBuild actually tried. - Add
pwdand afind "$CODEBUILD_SRC_DIR" -maxdepth 3 -type d -name disttopost_build, and compare. - Rewrite every path from the repository root, or set
base-directoryto the output folder and usefiles: '**/*'. - Check the folder name for your framework, and the case of every letter.
- Quote every pattern that starts with
*. - Make sure every listed file exists, including small ones like
appspec.ymlandimagedefinitions.json. - Confirm no earlier command failed, and that nothing in
post_builddeletes the output. - In CodePipeline, match secondary artifact identifiers to output artifact names exactly, and keep the top-level
files. - Unzip the artifact once and check its layout before you blame the deploy stage.
Frequently asked questions
What does "no matching artifact paths found" mean in CodeBuild?
None of the patterns under artifacts: files matched a file. CodeBuild checks them from the repository root, or from base-directory, after your commands finish.
Why does CodeBuild not find my dist folder?
Usually a cd in your commands. The build wrote web/dist, but artifact paths start at the repository root, so dist/**/* finds nothing. Use web/dist/**/*.
Are CodeBuild artifact paths relative to the current directory?
No. They are relative to the original build location ($CODEBUILD_SRC_DIR) or to base-directory if set. A cd during the build does not change them.
What is base-directory in buildspec artifacts?
The folder your files patterns start from. Its contents go into the artifact, but the folder itself does not, so base-directory: dist puts index.html at the top.
What does discard-paths do in CodeBuild?
With discard-paths: yes, every file is placed at the top of the artifact and folder structure is dropped. The default, no, keeps the folders.
How do I include all files in CodeBuild artifacts?
Use files: - '**/*' in quotes. Add base-directory to start from your output folder instead of the repository root.
Why must **/* be quoted in buildspec.yml?
In YAML an unquoted value starting with * is read as an alias, so the buildspec fails to parse. Write '**/*' with quotes.
Does one missing file fail the whole artifacts step?
No. As long as one pattern matches, CodeBuild logs "Skipping invalid file path" for the missing one and succeeds. The error only appears when nothing matches at all.
Does CodeBuild upload artifacts if the build fails?
Yes. A failure in the BUILD phase still runs POST_BUILD and the artifacts step, so partial files can be uploaded. Use unique artifact names and check CODEBUILD_BUILD_SUCCEEDING.
Are hidden files included in CodeBuild artifacts?
Yes. '**/*' includes dot files such as .env and dot folders such as .cache. Empty folders are left out. Use exclude-paths with '**/.*' and '**/.*/**/*' to drop them.
How do I exclude files from CodeBuild artifacts?
Add exclude-paths under artifacts, relative to base-directory, for example '**/*.map' or '**/.*' for dot files.
Why is my CodeBuild artifact a folder and not a zip?
The project's artifact packaging is NONE, the default. Set packaging to ZIP in the build project to get a single .zip object.
How do I give each CodeBuild artifact a unique name?
Turn on semantic versioning (overrideArtifactName) in the project, then set name: app-$CODEBUILD_BUILD_NUMBER or a $(date) expression in the buildspec.
How do I output multiple artifacts in CodePipeline from CodeBuild?
Define each under secondary-artifacts with an identifier that exactly matches a pipeline output artifact name, and keep the top-level files list.
What is CODEBUILD_SRC_DIR?
The folder where CodeBuild placed your primary source, the repository root. Other pipeline inputs get CODEBUILD_SRC_DIR_ plus the input artifact name.
Why does CodeBuild say "Failed to upload artifacts: Invalid arn"?
The S3 output bucket is in a different Region from the build project. Use a bucket in the same Region.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Developer – Associate (DVA-C03 from December 1, 2026) • DevOps Engineer – Professional (DOP-C02) • CloudOps Engineer – Associate (SOA-C03).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. A CodeBuild project runs cd frontend and npm run build, then fails in UPLOAD_ARTIFACTS with no matching artifact paths found for dist/**/*. What is the fix?
Artifact paths are relative to the original build location, so use frontend/dist/**/*, or set base-directory: frontend/dist with files: '**/*'.
2. A static website deployed by the CodePipeline S3 deploy action returns an error at the root, and the bucket contains web/dist/index.html. What should change?
Set base-directory: web/dist in the buildspec artifacts so the folder itself is left out and index.html is at the top of the artifact.
3. A CodeBuild action in CodePipeline must produce two output artifacts. How must the buildspec define them?
Under secondary-artifacts, with identifiers that exactly match the pipeline's output artifact names, while keeping the top-level artifacts files list.
4. A CodeBuild action has two input artifacts. Which setting decides where the buildspec is read and the commands run?
PrimarySource; the other inputs are available in $CODEBUILD_SRC_DIR_
5. A CodeBuild build fails in UPLOAD_ARTIFACTS with Failed to upload artifacts: Invalid arn. What is the likely cause?
The S3 output bucket is in a different AWS Region from the build project.
6. A team needs every CodeBuild artifact kept with a unique name instead of being overwritten. What should they configure?
Enable semantic versioning (overrideArtifactName) on the project and set a buildspec name such as app-$CODEBUILD_BUILD_NUMBER.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
The shop's builds have been green since. The fix took one line, base-directory: web/dist, and the lesson took one sentence, which Ethan wrote at the top of the buildspec as a comment for whoever edits it next: every path starts at the repository root. Jake reads it every time he opens the file. "It's the only comment in there I understand," he said. "So it's my favorite."
📌 If you keep one line from this page
CodeBuild finds artifacts from the repository root (or base-directory), never from where your commands left the shell; set base-directory to your output folder, use files: '**/*', and look for "Found N file(s)" in the log.
UPLOAD_ARTIFACTS · CLIENT_ERROR · $CODEBUILD_SRC_DIR · base-directory · discard-paths · secondary-artifacts · October 2026.
Revision note. Written October 22, 2026, with the CodeBuild buildspec reference, CodePipeline quotas of that date, and build logs from the CodeBuild agent. "Measure twice, cut once," the proverb says, and a buildspec path is measured from the front door.