Skip to content

feat: persist minimal deploy state to deployed-state.json - #2105

Merged
notgitika merged 3 commits into
refactorfrom
feat/deployed-state-top-level
Aug 27, 2026
Merged

feat: persist minimal deploy state to deployed-state.json#2105
notgitika merged 3 commits into
refactorfrom
feat/deployed-state-top-level

Conversation

@notgitika

@notgitika notgitika commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Moves the deploy-state file to a committed agentcore/.cli/deployed-state.json, and stops storing a full snapshot of every resource.

State is now keyed per target and holds only the deployed CloudFormation stack ARN (captured from the toolkit deploy result) plus the imperatively-created credential ARNs the synth step needs before the stack exists. Everything else is meant to be read live from CloudFormation, so the file can't go stale.

Changes

  • New DeployedState schema + readDeployedState / updateTargetState (merge-not-clobber: preserves sibling targets and keys this CLI doesn't own).
  • CDK toolkit runner now surfaces stackArn; CdkBackend.deploy persists it per target after a successful deploy.
  • Vended CDK app reads the new top-level path.

Notes

Move the deploy-state file from agentcore/.cli/deployed-state.json to a
committed agentcore/deployed-state.json, and stop storing a full snapshot
of every resource. State is now keyed per target and holds only the
deployed CloudFormation stack ARN (captured from the toolkit deploy
result) plus the imperatively-created credential ARNs the synth step
needs. Everything else is read live from CloudFormation, so the file
never goes stale.

Adds a DeployedState schema with readDeployedState/updateTargetState
(merge-not-clobber, preserving sibling targets and unowned keys), surfaces
stackArn from the CDK toolkit runner, and points the vended CDK app at the
new path.
@github-actions github-actions Bot added the size/m PR size: M label Aug 25, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Aug 25, 2026
@codecov-commenter

codecov-commenter commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.37%. Comparing base (e51a676) to head (d7bf768).
⚠️ Report is 11 commits behind head on refactor.

Additional details and impacted files
@@            Coverage Diff            @@
##           refactor    #2105   +/-   ##
=========================================
  Coverage     97.36%   97.37%           
=========================================
  Files           424      425    +1     
  Lines         25571    25641   +70     
=========================================
+ Hits          24897    24967   +70     
  Misses          674      674           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@agentcore-devx-automation agentcore-devx-automation Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AgentCore Harness Review

Verdict: Changes requested

The CDK app asset now reads agentcore/deployed-state.json, but the currently active deploy pipeline still writes credential ARNs to the old agentcore/.cli/deployed-state.json. The new CdkBackend/updateTargetState isn't wired into any production code path (no new CdkBackend( outside tests), so nothing writes to the new location during a real deploy. That breaks any project using OAuth or payment credentials.

Serious issue: split-brain read/write paths on the live deploy path

  • src/assets/cdk/bin/cdk.ts:131 now reads path.join(configRoot, 'deployed-state.json').
  • Pre-synth identity setup in src/cli/commands/deploy/actions.ts:213-223 (and the mirror in src/cli/tui/screens/deploy/useDeployFlow.ts:295-309) still writes via configIO.writeDeployedState(...), which resolves to <baseDir>/.cli/deployed-state.json via PathResolver.getStatePath() (src/lib/schemas/io/path-resolver.ts:195).
  • Post-deploy state persistence in src/cli/commands/deploy/actions.ts:417-432 writes to the same .cli/ path.

Concretely, in the current production flow (deploy actions.tssynthesizeCdkbin/cdk.ts):

  1. Pre-deploy writes credential ARNs to agentcore/.cli/deployed-state.json.
  2. CDK synth reads agentcore/deployed-state.json — which is either absent or stale (falls into the catch and leaves deployedState undefined).
  3. credentials resolves to undefined. For a project with payments, this throws with a misleading “Run agentcore deploy so the credential provider is created first” error at src/assets/cdk/bin/cdk.ts:180-186 on the first (and every) deploy. For OAuth/API-key harness bindings, the ARN is silently dropped from the synthesized stack.

Fix options (choose one; each needs to land in the same PR as the asset change to avoid a regression):

  1. Point PathResolver.getStatePath() at the new top-level path (src/lib/schemas/io/path-resolver.ts:195) so every existing configIO.readDeployedState/writeDeployedState caller (deploy actions, TUI deploy flow, teardown, status, invoke, session, imports, dev flows, evals, fetch-access, etc.) automatically uses agentcore/deployed-state.json. Also update the .gitignore template at src/cli/operations/init/files.ts:13-19 — the .cli/* + !.cli/deployed-state.json exception no longer matches the new path, and existing projects on disk still have .cli/deployed-state.json and will need either a migration or a compatibility read.
  2. Revert the read-path change in src/assets/cdk/bin/cdk.ts and defer the top-level move until the surrounding CLI is migrated in the same or a preceding change.

Either way, please add coverage that exercises the real read/write pairing (a bin/cdk.ts-level test, or an integration test that runs pre-deploy identity setup and then re-reads via the same path the asset uses). The current unit tests all wire updateTargetState to itself, so the mismatch with the live writers isn't caught.

Minor notes (not blockers)

  • The comment at src/assets/cdk/bin/cdk.ts:127-128 says the file is “committed, not the gitignored .cli/ dir,” but the old file was already committed via the !.cli/deployed-state.json exception in the init .gitignore. Consider rewording to reflect the real motivation (top-level visibility / minimal-schema restart).
  • Existing projects on disk will have stale agentcore/.cli/deployed-state.json after upgrading. Worth deciding whether to auto-migrate on next command, warn, or just document.

@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Aug 25, 2026
@notgitika

Copy link
Copy Markdown
Contributor Author

just ignore the harness reviewer :/

Hweinstock
Hweinstock previously approved these changes Aug 25, 2026
return JSON.parse(await Bun.file(statePath(root)).text());
}

describe("readDeployedState", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a way to test this behavior through the handlers? The advantage being that implementation details here could change, but we still observe the same behavior e2e.

If that isn't really possible, than maybe one level higher in cdk?

Comment thread src/assets/cdk/bin/cdk.ts
* A resource map provided in the patch replaces the previous map for that kind
* wholesale, so a credential dropped from the spec stops being advertised.
*/
export async function updateTargetState(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a bit hard to see if we need these utilities without seeing where they're used. It seems like the real purpose of this PR is to add the needed DeployedState types. Is that right?

@aidandaly24 aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I found several deployed-state integrity cases that I think need to be addressed before this merges.

Comment thread src/assets/cdk/bin/cdk.ts Outdated
let deployedState: Record<string, unknown> | undefined;
try {
deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8'));
deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, 'deployed-state.json'), 'utf8'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think malformed deployed state can leave us in a bad partial state here. This catches the parse error and synthesis continues, so AWS deployment can complete before updateTargetState() rereads the same invalid file and throws. The command then reports failure after mutating AWS without recording the new stack ARN. We should only treat a missing file as absent and validate existing state before deployment.

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!

patch: Partial<TargetState>,
): Promise<DeployedState> {
const statePath = statePathFor(projectRoot);
const state = await readDeployedState(json, projectRoot);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The “never drops another target” guarantee only holds for sequential updates. I reproduced two concurrent updates both reading empty state and the final file containing only prod. If concurrent deployments are not supported, I think we should remove or qualify this guarantee rather than imply the merge is generally safe.

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.

i qualified the comment

* Result of a CDK operation. `stackArn` is the ARN of the deployed stack (only
* a deploy produces one); bootstrap leaves it undefined.
*/
export type CdkRunResult = { outputs: CdkOutputs; stackArn?: string };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

stackArn should probably be required for deploy results. CDK’s DeployedStack defines it as required, but CdkRunResult makes it optional and the backend silently skips persistence when it is absent. That leaves a successful deployment that later commands cannot resolve. I think we should fail instead of accepting a deploy result without the ARN.

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.

done

*/
export const DEPLOYED_STATE_RELATIVE_PATH = join("agentcore", "deployed-state.json");

const CredentialStateSchema = z.object({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One edge case here: Zod strips fields not listed in CredentialStateSchema. A stack-ARN-only update reads and rewrites the whole file, so it can delete future or unowned fields inside an existing credential entry. I think this nested schema should use .passthrough() too.

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

targets: { ...state.targets, [targetName]: merged },
};

await json.write(statePath, next);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This state file should use the existing atomicWrite() helper. FsReadWriteJson.write() truncates the authoritative file directly, so an interruption or disk failure can leave malformed JSON that blocks later deploys.

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.

done! thanks for the thorough review!

Address review of the deployed-state work:
- Validate any existing state before deploy, so a malformed file fails
  before AWS is mutated rather than after (leaving the new stack ARN
  unrecorded). The vended app likewise only treats a missing file as
  absent and surfaces a malformed one.
- Require a stack ARN on a deploy result; a successful CDK deploy always
  has one, so its absence is malformed -- fail instead of silently
  skipping persistence.
- Write the state file atomically (temp + rename) so an interruption can't
  leave unparseable JSON that blocks later deploys.
- Passthrough the credential-entry schema so a stack-ARN-only rewrite
  doesn't strip fields a newer CLI records.
- Qualify the merge guarantee: safe for sequential deploys, not concurrent.
@github-actions github-actions Bot added size/m PR size: M and removed size/m PR size: M labels Aug 27, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
Move the state file back under agentcore/.cli/ to match the released CLI's
location, so a project created by an older CLI keeps reading the same path
after upgrading (the vended app isn't re-vended on deploy). The scaffolded
.gitignore ignores the rest of .cli/ but re-includes deployed-state.json, so
the stack binding + credential ARNs stay committed and shared.
@github-actions github-actions Bot added size/m PR size: M and removed size/m PR size: M labels Aug 27, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 27, 2026
@notgitika notgitika changed the title feat: persist minimal deploy state to top-level deployed-state.json feat: persist minimal deploy state to deployed-state.json Aug 27, 2026
@github-actions github-actions Bot added size/m PR size: M and removed size/m PR size: M labels Aug 27, 2026
@notgitika
notgitika merged commit 55d04b6 into refactor Aug 27, 2026
37 checks passed
@notgitika
notgitika deleted the feat/deployed-state-top-level branch August 27, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants