<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vamshidher Reddy Jannapu Reddy</title>
    <description>The latest articles on DEV Community by Vamshidher Reddy Jannapu Reddy (@moneytool).</description>
    <link>https://dev.to/moneytool</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4153436%2F456562f5-94ad-4e4b-b38b-7a09e0ff7534.png</url>
      <title>DEV Community: Vamshidher Reddy Jannapu Reddy</title>
      <link>https://dev.to/moneytool</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moneytool"/>
    <language>en</language>
    <item>
      <title>My agent guardrail only lived on the laptop. So I compiled it into AWS.</title>
      <dc:creator>Vamshidher Reddy Jannapu Reddy</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:08:30 +0000</pubDate>
      <link>https://dev.to/moneytool/my-agent-guardrail-only-lived-on-the-laptop-so-i-compiled-it-into-aws-j1d</link>
      <guid>https://dev.to/moneytool/my-agent-guardrail-only-lived-on-the-laptop-so-i-compiled-it-into-aws-j1d</guid>
      <description>&lt;p&gt;For the past few weeks I've been building &lt;a href="https://github.com/moneytool/aegis-devops" rel="noopener noreferrer"&gt;Aegis-DevOps&lt;/a&gt;, an open-source check that sits in front of the shell commands an AI coding agent runs. Before Claude Code, Codex, Copilot, Cursor or Gemini CLI runs &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;terraform&lt;/code&gt; or &lt;code&gt;aws&lt;/code&gt;, a hook hands the command to Aegis, and Aegis blocks it if your signed policy says no. It works. It also has a hole I knew about from the start, and version 0.3.0 is my first attempt at closing it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I drafted this article with help from an AI assistant and reviewed it myself. The commands and output below are from running aegis-devops 0.3.0 against its example policy.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hole: a hook only sees commands
&lt;/h2&gt;

&lt;p&gt;A pre-execution hook sees the command string the agent asks its shell tool to run. It doesn't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Python script the agent writes and runs, which calls &lt;code&gt;boto3&lt;/code&gt; directly;&lt;/li&gt;
&lt;li&gt;an SDK call from code the agent is "just testing";&lt;/li&gt;
&lt;li&gt;a credential the agent found in a file and used from somewhere else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all three cases the destructive call reaches the AWS API without ever looking like &lt;code&gt;aws s3 rb&lt;/code&gt;. Blocking on the client is the right first layer, because it's fast and it can explain itself to the model. It isn't the last layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: let AWS refuse the call
&lt;/h2&gt;

&lt;p&gt;AWS already has a control that sits above everything an identity inside an account can do: a &lt;strong&gt;Service Control Policy&lt;/strong&gt;. An SCP attached to an account in an AWS Organization caps what any role in that account may do, and an agent with IAM rights inside the account can't detach it.&lt;/p&gt;

&lt;p&gt;So 0.3.0 adds a compiler. It takes the same policy the hook enforces and writes it out as SCPs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aegis compile aws --account 111122223333 --out build/aegis-aws
aegis: wrote 1 SCP(s) for account 111122223333 to build/aegis-aws
       (exact=2, not-applicable=27, partial=1)
aegis: agents.yaml is report-only: the SCPs are under report-only/ and are not for attaching;
       review coverage.md, then set enforcement: enforce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule "never delete an S3 bucket or an RDS database" becomes an explicit &lt;code&gt;Deny&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rds:DeleteDBInstance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rds:DeleteDBCluster"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rds:DeleteDBSnapshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="s2"&gt;"rds:DeleteDBClusterSnapshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObjectVersion"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ArnNotLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:PrincipalArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::111122223333:role/BreakGlass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                         &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::111122223333:role/Deploy"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringNotEqualsIfExists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:SourceIdentity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it doesn't matter whether the agent typed &lt;code&gt;aws s3 rb&lt;/code&gt;, wrote a boto3 script, or found a key. If the call is made as an agent identity, AWS refuses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who counts as an agent?
&lt;/h2&gt;

&lt;p&gt;An API call carries an identity, not a hint about who was typing. So the compiler needs an identity model, in a signed &lt;code&gt;agents.yaml&lt;/code&gt;. The default is &lt;strong&gt;deny-by-default&lt;/strong&gt;: you list the identities you trust (people, your deploy role, CI bound to a specific workflow), plus at least one break-glass role, and everything else is treated as an agent.&lt;/p&gt;

&lt;p&gt;I went with deny-by-default because the other way round, listing the agents, fails open. Forget one, or let an agent mint a new role, and the new one isn't restricted.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent will try to become trusted
&lt;/h2&gt;

&lt;p&gt;If the only thing between an agent and &lt;code&gt;DeleteDBInstance&lt;/code&gt; is "is this call from the Deploy role?", the obvious move is to become the Deploy role. So every compile also adds statements that stop exactly that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no &lt;code&gt;sts:AssumeRole&lt;/code&gt; into a trusted or break-glass role, and no &lt;code&gt;iam:PassRole&lt;/code&gt; of one;&lt;/li&gt;
&lt;li&gt;no edits to a trusted role's trust policy or permissions, and no new credentials for a trusted user;&lt;/li&gt;
&lt;li&gt;no setting an exempt source identity on a session;&lt;/li&gt;
&lt;li&gt;no &lt;code&gt;organizations:LeaveOrganization&lt;/code&gt;, since an account that leaves the organization sheds its SCPs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part I care about most: saying what it doesn't cover
&lt;/h2&gt;

&lt;p&gt;A guardrail that looks stronger than it is does more damage than no guardrail, because people stop checking. So every compile writes a coverage report, rule by rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### block-s3-bucket-delete: exact, mapping verified
- enforced: s3/bucket delete (s3:DeleteBucket, s3:DeleteObject, s3:DeleteObjectVersion)
- same effect, not covered: s3:PutLifecycleConfiguration: an expiration rule deletes every object
- same effect, not covered: s3:PutBucketPolicy: a deny-all bucket policy locks everyone out

### block-rds-delete: partial, mapping verified
- not enforced: 'rds/*' also matches resource types the action map does not know
- same effect, not covered: rds:ModifyDBInstance: BackupRetentionPeriod=0 deletes the automated backups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Same effect, not covered" is the list of other ways to reach the same outcome that the rule, as written, doesn't block. You can widen the rule, or accept the gap knowingly.&lt;/p&gt;

&lt;p&gt;A few more choices along the same lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Report-only first.&lt;/strong&gt; The first compile writes SCPs into &lt;code&gt;report-only/&lt;/code&gt; and tells you not to attach them. You switch &lt;code&gt;agents.yaml&lt;/code&gt; to &lt;code&gt;enforce&lt;/code&gt; (a signed change) once you've reviewed who would be restricted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aegis never holds cloud write credentials.&lt;/strong&gt; It writes JSON, and you apply it with your own infrastructure as code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unverified rules never reach a compiled policy.&lt;/strong&gt; The compiler starts from a verified snapshot, so a rule that was tampered with, forged, or written by someone not allowed to write it gets no vote, the same as on the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every action mapping was tested&lt;/strong&gt; in a sandbox AWS Organization, with live calls and the IAM policy simulator. A mapping added later starts out marked unverified.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's still missing
&lt;/h2&gt;

&lt;p&gt;It's a preview. AWS is in the 0.3.0 release; a Kubernetes target (ValidatingAdmissionPolicies scoped to agent identities) is merged and waiting for the next release, and GCP and Azure are designed but not built. SCPs don't apply to an organization's management account, so agents shouldn't run there at all. Rules with a time window or a rate limit stay client-side, because an SCP can't express them. And it's a young project: I'd much rather hear "this mapping is wrong" now than after someone relies on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aegis-devops
aegis init .aegis
aegis compile aws &lt;span class="nt"&gt;--account&lt;/span&gt; &amp;lt;your-account-id&amp;gt; &lt;span class="nt"&gt;--out&lt;/span&gt; build/aegis-aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client hook installs in one command for each agent (&lt;code&gt;aegis install claude|codex|copilot|cursor|gemini|opencode&lt;/code&gt;), and there's a &lt;a href="https://github.com/marketplace/actions/aegis-devops-plan-check" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; for Terraform and OpenTofu plans. Details on the compiler are in &lt;a href="https://github.com/moneytool/aegis-devops/blob/main/docs/server-side.md" rel="noopener noreferrer"&gt;docs/server-side.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you run agents with real AWS access, I'd like to know how you scope them today. Issues are open at &lt;a href="https://github.com/moneytool/aegis-devops" rel="noopener noreferrer"&gt;github.com/moneytool/aegis-devops&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>Deleting your AWS Copilot stacks can delete your database. Here's why.</title>
      <dc:creator>Vamshidher Reddy Jannapu Reddy</dc:creator>
      <pubDate>Thu, 01 Oct 2026 02:51:27 +0000</pubDate>
      <link>https://dev.to/moneytool/deleting-your-aws-copilot-stacks-can-delete-your-database-heres-why-pen</link>
      <guid>https://dev.to/moneytool/deleting-your-aws-copilot-stacks-can-delete-your-database-heres-why-pen</guid>
      <description>&lt;p&gt;AWS ended support for the Copilot CLI on June 12, 2026 and archived the repository ten days later. If you deployed ECS services with Copilot, they keep running: Copilot was only ever a generator of CloudFormation stacks.&lt;/p&gt;

&lt;p&gt;The trouble starts when you try to leave. Most teams want those services in Terraform, and the obvious plan is "recreate it in Terraform, then delete the Copilot stacks." Done naively, that plan deletes production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four traps
&lt;/h2&gt;

&lt;p&gt;I read through Copilot's source to map exactly what happens when its stacks are deleted. Four things stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Custom-resource Delete handlers.&lt;/strong&gt; Copilot ships 13 Lambda-backed custom resources, and 9 of them do something destructive on Delete. When the environment stack goes, they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete the ACM certificate and its DNS validation records,&lt;/li&gt;
&lt;li&gt;delete every Route 53 alias record for your custom domains,&lt;/li&gt;
&lt;li&gt;delete the NS delegation record in the app's hosted zone,&lt;/li&gt;
&lt;li&gt;empty the ELB access-logs bucket, every object version included.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Importing those resources into Terraform first doesn't save you: the Lambda doesn't care who manages the certificate now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The env-controller.&lt;/strong&gt; Each service stack carries an &lt;code&gt;EnvControllerAction&lt;/code&gt; custom resource. When the last service that needs a shared ALB, NAT gateways or EFS is deleted, the env-controller updates the environment stack and removes them. Delete your last service stack and your load balancer and file system go with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unprotected addons.&lt;/strong&gt; Aurora, DynamoDB and S3 addons live in a nested stack with no &lt;code&gt;DeletionPolicy&lt;/code&gt;. Delete the parent and CloudFormation deletes your database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;--retain-resources&lt;/code&gt; doesn't help.&lt;/strong&gt; The flag everyone reaches for only works on stacks already in &lt;code&gt;DELETE_FAILED&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt in place instead of rebuilding
&lt;/h2&gt;

&lt;p&gt;The safe sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put &lt;code&gt;DeletionPolicy: Retain&lt;/code&gt; and &lt;code&gt;UpdateReplacePolicy: Retain&lt;/code&gt; on &lt;strong&gt;every&lt;/strong&gt; resource in &lt;strong&gt;every&lt;/strong&gt; stack, nested stacks and the StackSet included. Retain on a &lt;code&gt;Custom::*&lt;/code&gt; resource also stops CloudFormation from invoking its Delete handler.&lt;/li&gt;
&lt;li&gt;Import every resource into Terraform with the exact values it runs with today, so the first plan is import-only.&lt;/li&gt;
&lt;li&gt;Delete the Copilot stacks. CloudFormation forgets the resources; nothing is deleted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doing that by hand across dozens of resources is error-prone, so I built a tool for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  ecsodus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/moneytool/ecsodus" rel="noopener noreferrer"&gt;ecsodus&lt;/a&gt; is an open-source CLI (Apache-2.0) that does exactly that sequence and nothing else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx ecsodus inventory &lt;span class="nt"&gt;--app&lt;/span&gt; myapp &lt;span class="nt"&gt;-o&lt;/span&gt; inventory.json   &lt;span class="c"&gt;# read-only discovery&lt;/span&gt;
uvx ecsodus report    inventory.json                  &lt;span class="c"&gt;# REPORT.md: what's safe, what's blocked, why&lt;/span&gt;
uvx ecsodus generate  inventory.json &lt;span class="nt"&gt;--out&lt;/span&gt; infra/     &lt;span class="c"&gt;# Terraform + retain patches + RUNBOOK.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmoneytool%2Fecsodus%2Fmain%2Fdocs%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmoneytool%2Fecsodus%2Fmain%2Fdocs%2Fassets%2Fdemo.gif" alt="ecsodus demo" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design choices that matter for a tool like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-only by construction.&lt;/strong&gt; Every AWS client is wrapped in a guard that refuses anything but &lt;code&gt;Describe&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Get&lt;/code&gt; and &lt;code&gt;Lookup&lt;/code&gt;. ecsodus never applies Terraform, deletes anything or moves traffic. You run the runbook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact imports.&lt;/strong&gt; Each resource becomes a flat &lt;code&gt;aws_*&lt;/code&gt; block built from literal deployed values, and &lt;code&gt;ecsodus check --phase import&lt;/code&gt; rejects any plan with an update, create, delete or replacement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy-only change sets.&lt;/strong&gt; The retain patch edits the deployed template line by line, and &lt;code&gt;check --changeset&lt;/code&gt; accepts a change set only if it changes deletion policies and nothing else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never split a stack.&lt;/strong&gt; If anything in a stack is unsupported, the whole stack (and everything that depends on it) stays on Copilot, and the report says why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An honest baseline.&lt;/strong&gt; Every report starts with the zero-risk option: keep the CloudFormation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Does it work?
&lt;/h2&gt;

&lt;p&gt;On September 30 I ran it end to end against a real Copilot v1.34.1 app (an environment, a Load Balanced Web Service, DynamoDB and S3 addons; 5 stacks, 49 resources) in a sandbox account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;44 of 44 resources imported, 0 changed, 0 destroyed.&lt;/li&gt;
&lt;li&gt;Every Copilot stack and the StackSet deleted.&lt;/li&gt;
&lt;li&gt;The service kept returning HTTP 200, and the sentinel data in DynamoDB and S3 survived.&lt;/li&gt;
&lt;li&gt;The env-controller and rule-priority Lambdas were never invoked on teardown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full report is in the repo: &lt;a href="https://github.com/moneytool/ecsodus/blob/main/docs/e2e/2026-09-30-aws-e2e.md" rel="noopener noreferrer"&gt;docs/e2e/2026-09-30-aws-e2e.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do yet
&lt;/h2&gt;

&lt;p&gt;It's alpha. v0.1 supports Load Balanced Web Services and Backend Services, their environment, and Aurora/RDS, DynamoDB and S3 addons. Worker Services, Scheduled Jobs, Request-Driven Web Services, Static Sites, NLB, CloudFront, sidecars and pipelines are detected and reported as blocked. App Runner is next.&lt;/p&gt;

&lt;p&gt;If you're still running something on Copilot, I'd like to hear what it is. Run &lt;code&gt;ecsodus inventory&lt;/code&gt; and &lt;code&gt;ecsodus report&lt;/code&gt; (both read-only) and open an issue with what's blocked: that's how the roadmap gets set. And if the tool saves you a bad afternoon, a star on &lt;a href="https://github.com/moneytool/ecsodus" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other Copilot users find it.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
