ReviewOS

stacks/ts-cloud

`listBuckets()` returns 0 on `stacks` profile despite $191/mo S3 spend

#105
Closed glennmichael123 opened this 24 days ago · 0 comments
24 days ago

Symptom

Running cost:analyze --profile stacks against our stacks AWS profile produces:

Service                         Resources    Cost     % of Total
Amazon Simple Storage Service   0 buckets    $191.91  62.4%
...
Total: $307.63 across 19 services

S3 is 62.4% of the bill ($191.91 in April 2026), but listBuckets() reports 0 buckets. That's not internally consistent — you can't be charged $191/mo for S3 with zero accessible buckets.

What's happening in code

packages/ts-cloud/bin/commands/cost.ts (registerCostCommands → cost:analyze):

let s3Buckets: number | null = null
if (services.some(s => s.service === S3_SERVICE_NAME)) {
  try {
    const result = await new S3Client('us-east-1', profile).listBuckets()
    s3Buckets = result.Buckets?.length ?? 0
  }
  catch {
    // listBuckets needs s3:ListAllMyBuckets — silently fall back to '-'
  }
}

So listBuckets() either:

  • Succeeded but returned Buckets: undefined or Buckets: [] → we report 0 buckets. But the account is being billed for S3.
  • Threw → we'd fall back to -, not 0. Since we report 0, the call succeeded.

Hypotheses (in rough order of likelihood)

  1. The stacks profile is scoped to an IAM role/user that can reach Cost Explorer (account-wide billing data) but cannot list buckets. ListBucket might be silently authorized but return an empty list on a permissions edge case rather than throwing.
  2. new S3Client('us-east-1', profile) is binding to the wrong region/endpoint and getting an empty bucket list there even though buckets live elsewhere. Note: ListBuckets is supposed to be a global API (region-agnostic), but our client may be region-specific in a way that affects the response.
  3. The S3Client implementation itself has a bug that swallows the actual bucket list (parsing, signature, header).
  4. Buckets are owned by a sub-account in an Organization and the stacks profile credentials are at a level that sees billing roll-up but not the sub-account's buckets.

Repro

cd packages/ts-cloud
bun bin/cli.ts cost:analyze --profile stacks

…against any AWS profile where the Cost Explorer report shows non-trivial S3 spend but listBuckets() returns 0.

Acceptance

  • Identify which hypothesis is correct.
  • Either fix the S3Client bug, fix the region binding, or improve the cost:analyze output to distinguish "0 buckets visible" from "can't enumerate buckets — IAM scoping" (don't quietly report 0 when we can't actually tell).
  • Bonus: if it's an IAM scoping issue, update the docs / README to call out which IAM perms cost:analyze actually needs to be useful (ce:GetCostAndUsage + s3:ListAllMyBuckets at minimum).
  • #103 — original AWS bill investigation; this anomaly is the immediate follow-up.
  • packages/ts-cloud/src/aws/s3.ts — S3Client implementation.

Sign in to comment on this issue.