Hey folks, been running FOSSA for about six months now, and we just finished rolling it out to the entire dev team — roughly 100 engineers working in our TypeScript/Go monorepo. Overall, it's been a game-changer for license compliance and SBOM generation, but wow, the monorepo setup really threw us some curveballs. 😅
I figured I'd share what actually broke or needed serious tuning, hoping it saves someone else a weekend.
* **Scan times went through the roof on the first run.** The default configuration tried to analyze every package.json and go.mod in the entire tree as if they were independent projects. We saw scans taking over 90 minutes, which totally broke our CI feedback loop. The key was switching to a **monorepo-aware analysis mode** and using `.fossa.yml` files at the root of key service directories to define modules properly.
* **False positives on internal packages.** Our shared internal libraries were being flagged as "unlicensed" or pulled from the wrong VCS location. We had to manually define dependencies in the FOSSA project config to mark them as internal and exempt them from certain policies.
* **The "deep" vs "shallow" dependency resolution debate.** For our Go services, the deep analysis was creating massive, noisy reports because of the way the toolchain fetches dependencies. We ended up setting shallow scans for Go and reserving deep scans only for our frontend npm packages, which needed the full tree for audit purposes.
On the plus side, once we got the configuration dialed in, the integration with our PR checks is fantastic. It's catching incompatible licenses on new dependencies before they get merged, which is exactly what we wanted.
Has anyone else hit similar issues with a large monorepo? I'm especially curious if you found a better way to handle the performance tuning for mixed-language repos.
—Chris
K8s enthusiast
Oh yeah, the internal packages one is such a classic headache! We had the same issue where FOSSA kept trying to fetch our shared `@company/utils` package from a public registry. Our fix was a bit different though - we ended up using the `experimental: allow-unresolved: true` flag in our `.fossa.yml` and then explicitly listing those internal packages in a custom policy. It feels a bit hacky, but it stopped the false alarms.
The scan time problem is brutal. Even after switching modes, we found we had to aggressively prune which directories were considered for analysis. Did you guys set up any custom excludes for things like `node_modules` inside nested `packages/` or `dist` folders? I'm curious if your config looks similar to ours now.
Integration Ian
The jump to 90 minute scan times is a huge cost multiplier in cloud CI environments. That's a lot of compute time burning money while everyone waits.
We hit a similar wall and found the monorepo mode helped, but the real fix was restructuring the analysis to run only on changed paths in our PRs. We use a custom script with `git diff` to identify which service directories had modifications, then only run FOSSA on those. It cut our average scan time down to under 7 minutes and saved about $1400 a month on our CI bill.
Did you consider a targeted, diff-based approach, or was setting the global analysis mode sufficient for your team?
CloudCostHawk
Yep, the monorepo mode was a lifesaver for us too. The internal packages issue is still a bit of a pain though, even with manual definitions in the project config. We've found we have to update it pretty much every time someone spins up a new internal lib, which is a chore.
I'm curious about your last point - what was the outcome of the deep vs shallow resolution debate for your team? We went with shallow to keep scans fast, but I sometimes wonder if we're missing something in the nested deps.
Self-host or die trying.
Oh man, the 90 minute initial scan hits home. We had a similar panic moment! The monorepo mode was definitely step one, but we found our real speedup came from tweaking the parallelism settings in our CI runner. Turns out letting it go wild on CPU cores was just thrashing the node. Dialing that back actually got us to a stable 25 minutes for a full repo scan, which was good enough for our nightly job.
The internal package thing is such a ongoing battle, isn't it? We tried the manual definitions route too but keeping it updated was a nightmare. We ended up writing a small pre-scan script that auto-generates that section of the `.fossa.yml` by crawling our internal package registry. Saves a ton of manual toil.
On deep vs shallow, we went deep for our main branch scans and shallow for PRs. It felt like a decent compromise between safety and speed. Do you guys run it differently?
cost first, then scale
That's a clever idea to auto-generate the internal package definitions. Did you have to handle version differences between branches, or does your script just pull the latest from the registry?
I like your compromise on deep vs shallow scans. We've been debating a similar split, but I worry shallow scans on PRs might miss a new license introduced by a nested dependency update. Have you ever caught a license issue that way?
Good question on the version differences. Our script actually runs as part of the scan itself, so it just pulls whatever is in the working tree for that branch. It parses the local package manifests rather than hitting the registry, so it's always in sync with the commit being scanned.
On the shallow PR scans, that's exactly the risk. We haven't caught a new license that way yet, but we've definitely had a few close calls where a transitive dep update introduced a vulnerability that the shallow scan missed. That makes me think the license scenario is just a matter of time.
Have you looked at whether FOSSA's diff analysis can flag new dependencies introduced at *any* depth, or does it only track the direct ones? That seems like the key piece for a shallow PR check.
Switching to monorepo-aware mode was the correct first step, but it's not a silver bullet. Did you validate the SBOM output for completeness after that change? I've seen teams accidentally exclude whole swaths of dependencies that way.
The internal package problem is a governance red flag. Manually defining them works, but it's a process control failure. You need an automated inventory, otherwise your audit trail is broken. Did you implement any controls to track when a new internal package is created and ensure it's added to the FOSSA config?
Where is your SOC 2?
Yeah, everyone always jumps straight to the monorepo mode switch like it's a magic wand. Did you actually verify the SBOM outputs afterward? I've seen teams turn it on, get the green checkmark, and then discover they're now missing half their transitive dependencies because the analyzer got confused by a custom workspace layout. The false confidence is worse than a slow scan.
The internal package thing is a process smell. If you're manually curating a list in a config file, you've already lost. That list is guaranteed to drift the moment someone creates a new internal lib and forgets to update it. You might as well be maintaining a spreadsheet.
You mentioned defining modules at the root of key service directories. That's fine, until you realize a new team spun up a "temp-experimental" service three months ago and never added a .fossa.yml. You're now scanning it as one giant blob with default settings. Hope they didn't import anything fun.
Trust but verify
You're absolutely right about the false confidence being worse than a slow scan. We did a manual spot check on the SBOM after enabling monorepo mode and found a whole section of our shared UI components missing, because their `package.json` files lived in a `libs/` directory that didn't match FOSSA's default path patterns.
That "temp-experimental" scenario is exactly why we moved to a lint rule. Our CI now fails the build if a new service directory is created without a `.fossa.yml` stub. It's not perfect, but it stops the complete blind spots from forming.
The manual list drift is inevitable. We solved it by adding a pre-commit hook that parses our workspace configuration and automatically appends new internal packages to the project config. The file is still checked in, but it's generated.
Your point about the experimental service is exactly why the lint rule isn't enough. A stub config doesn't guarantee correct analysis. You need the generator to also inherit the proper module settings from the nearest valid parent config, otherwise you're just scanning a blob with defaults, like you said.
Show me the query.
Nice! The pre-commit hook for generating the config is a solid approach. That's way better than a simple lint rule.
We tried something similar but hit a snag with merge conflicts. If two developers add new internal packages in separate feature branches, the pre-commit hook updates the same `.fossa.yml` in each branch. It's a bit of a headache to resolve when they merge.
Did you run into that at all? We ended up moving the generation into the CI job itself, just before the scan runs, to avoid the conflict pain.
security by default
Yep, we hit the exact same merge conflict issue with the pre-commit hook. It felt like a step forward that immediately became a coordination problem.
Our current middle ground is to have the hook generate a *separate* config fragment (like `.fossa.autogen.yml`) that gets `.gitignore`'d, and then our CI job merges it with the base config right before scanning. That way, the checked-in file stays static, but we still get the automation benefits locally and in CI.
It adds a tiny bit of complexity to the pipeline, but it's worth it to avoid those "who touched the config last?" merge headaches.
Dashboards or it didn't happen.
That autogenerated fragment approach is clever, I like the way it sidesteps the merge conflict problem entirely. We actually went with something similar, but we're storing those fragments as separate JSON files in a `.fossa/` directory.
One thing to watch for: if your base config ever changes structure, your CI merge logic has to be smart enough to handle it. We broke our pipeline once by adding a custom analyzer type in the base config, and the naive concatenation just overwrote it. Took a bit to debug.
✌️
The config fragment merge issue is a real hidden pitfall. We got burned by that too, but in our case it was when we added a custom header to our API client for authenticated scans. The merge script just clobbered the new `headers` block.
Your point about storing them as separate JSON files in `.fossa/` is smart. It makes the merge step more explicit. Did you end up using a shallow merge library like lodash's, or did you write something custom to handle the nested structures?
Review first, buy later.