Skip to content

fix: Resolve the cloud provider when a resource search omits it - #68

Merged
liranfarage89 merged 5 commits into
mainfrom
apo-704-find_cloud_resources-returns-400s-to-ai-agents-that-omit
Sep 2, 2026
Merged

liranfarage89 merged 5 commits into
mainfrom
apo-704-find_cloud_resources-returns-400s-to-ai-agents-that-omit

Conversation

@liranfarage89

@liranfarage89 liranfarage89 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What

AI agents call get-cloud-resources without any filter and get a 400 back, about eight times a day in prod. The API needs either cloudConfigurationId or cloudProvider, and the tool schema only said so in prose, so agents kept leaving both out and the user saw a failed lookup.

Instead of demanding the filter, we now work it out. When a search arrives with neither field, the server reads the organization's cloud configurations. One provider means we fill it in and the search runs. More than one means the agent gets back a message naming them, so it can retry with the right one. None means the organization has no Cloud Compass data, which we now say plainly.

Also removed in from both cloudProvider and cloudConfigurationId in the schema. The API reads .eq on both, so an agent that used in got a 400 no matter what it sent.

Tests

First tests in this repo. npm test was npm test, an infinite loop, so it now runs Node's own test runner through tsx. No new dependencies. Added a npm test step to the lint workflow, otherwise nothing would run them.

Four cases in src/env0-service/env0-service.test.ts. Two of them fail on main and pass here.

Manual QA

Driven through the tool itself over stdio JSON-RPC against the dev API, not through the service class, so these are the responses an agent actually receives.

Single-provider organization, which is the case the fix exists for:

  • Created a throwaway AWS configuration in my own dev org so the org had exactly one provider. Bogus account and bucket, health: false, so no scan ran.
  • A search with filters: {} returned isError: false and {"resources":[],"total":0}. The provider was filled in and the API accepted the search. The result is empty because that bucket was never scanned, the 400 is what mattered.
  • The same payload sent straight to the API, which is what the old code sent, returned HTTP 400 You must specify at least one of the parameters in the filters.
  • Configuration deleted afterwards, the org lists none.

Other paths:

  • Multi-provider org (KuShield - SHAGs): a search with no filters returns Set filters.cloudProvider.eq to one of: AWS, AzureLAW, or set filters.cloudConfigurationId.eq. instead of the raw 400.
  • Same org, scoped by cloudConfigurationId.eq: resources come back, isError: false.
  • Same org, scoped by cloudProvider.eq: 1549 resources.
  • Org with no cloud configurations: No cloud configurations found for this organization.
  • tools/list shows cloudConfigurationId and cloudProvider each exposing only eq, both required inside their object, so the in trap is gone from the schema agents read.

Docker image, since that is what users actually run:

  • docker build succeeds. .dockerignore already lists **/*.test.ts, so the test file never enters the build context and neither src nor dist in the image carries it.
  • Ran the image itself against dev. Multi-provider org returns the provider list message, and a single-provider org returns isError: false with {"resources":[],"total":0}, so the fallback works in the shipped artifact and not just in tsx.

Fixes APO-704

🤖 Generated with Claude Code

The env0 API rejects a cloud resource search that has neither
cloudConfigurationId nor cloudProvider, and agents send neither about
eight times a day. When both are missing we now look up the
organization's cloud configurations: one provider means we fill it in,
several means the agent gets a message naming them, none means the
organization has no Cloud Compass data yet.

Also dropped cloudProvider.in from the tool schema. The API only reads
.eq, so an agent that used .in always got a 400.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@liranfarage89 liranfarage89 self-assigned this Sep 1, 2026
@liranfarage89
liranfarage89 requested a review from a team September 1, 2026 14:07

@alonnoga alonnoga left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Verified locally on the branch: tests 3/3, type-check, lint and prettier all pass, and the fallback logic looks right.

One thing to fix before merging: the test glob in package.json (inline comment). The rest are nits, take or leave.

  • tsc now emits dist/env0-service/env0-service.test.js and .d.ts into the Docker image. Harmless, but consider adding "exclude": ["**/*.test.ts"] to tsconfig.json or a separate tsconfig.build.json.
  • Every unfiltered search now costs an extra API call to /mcp/cloud/configurations. Fine at ~8/day, just noting in case volume grows.

Comment thread package.json Outdated
"start": "tsx src/cli.ts",
"dev": "tsx watch src/cli.ts",
"test": "npm test",
"test": "tsx --test src/**/*.test.ts",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix before merging: npm runs scripts under sh, which has no globstar, so ** behaves like *. Only tests one directory deep run (src/x/y.test.ts). A future src/mcp/tools/foo.test.ts would be silently skipped.

Quote the glob so Node expands it instead of the shell:

"test": "tsx --test 'src/**/*.test.ts'"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ff31aaa. Confirmed with a probe test at src/mcp/tools/: npm test found 3 tests before, 4 after.

// The API rejects a search that has neither cloudConfigurationId nor cloudProvider, and callers often send neither.
private async withCloudProvider(
filters: GetCloudResourcesParams['filters']
): Promise<GetCloudResourcesParams['filters']> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard checks cloudConfigurationId?.eq only, but the schema still allows cloudConfigurationId.in. An agent that sends in on a multi-provider org gets the "Set filters.cloudProvider.eq..." error even though it did scope the search.

Either check ?.eq || ?.in?.length, or drop in from cloudConfigurationId like you did for cloudProvider. Nit, your call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 18a6e8b by dropping in, same as cloudProvider. The env0 API's own guard reads filters.cloudConfigurationId.eq only, so in alone got a 400 there too. Widening the guard would just have moved the 400 one layer down.

return { service: new Env0Service(config, client), requests };
};

describe('getCloudResources', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no case for the zero-configurations path ("No cloud configurations found"). One more buildService([]) + assert.rejects would cover it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2fe79f9.

@liranfarage89

Copy link
Copy Markdown
Contributor Author

On the two notes in the review body:

dist and the image: the published image is already clean. .dockerignore lists **/*.test.ts and the Dockerfile builds from COPY . ., so the test file never enters the build context and tsc never sees it. I also tried "exclude": ["**/*.test.ts"] in tsconfig.json and it breaks eslint (parserOptions.project can no longer find the file) and drops the test from npm run type-check, so I left it alone. Only a local npm run build emits dist/**/*.test.js, and nothing ships that.

Extra API call: agreed, and it only happens on searches that used to 400, so it costs a call where we previously returned nothing useful. If volume grows the providers list is a good cache candidate.

@liranfarage89
liranfarage89 merged commit b77ab43 into main Sep 2, 2026
5 checks passed
@liranfarage89
liranfarage89 deleted the apo-704-find_cloud_resources-returns-400s-to-ai-agents-that-omit branch September 2, 2026 06:56
@envzero-ci envzero-ci Bot mentioned this pull request Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants