diff --git a/cli/src/commands/app/app_metadata.ts b/cli/src/commands/app/app_metadata.ts index a1f524fbcd..ce8dcf8da9 100644 --- a/cli/src/commands/app/app_metadata.ts +++ b/cli/src/commands/app/app_metadata.ts @@ -92,7 +92,12 @@ async function generateAppHash( } } - return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; + // Sort keys so the top hash is deterministic regardless of filesystem readdir order + const sortedHashes: Record = {}; + for (const k of Object.keys(hashes).sort()) { + sortedHashes[k] = hashes[k]; + } + return { ...sortedHashes, [TOP_HASH]: await generateHash(JSON.stringify(sortedHashes)) }; } /** diff --git a/cli/src/commands/flow/flow_metadata.ts b/cli/src/commands/flow/flow_metadata.ts index e59d27bab9..4cda34f582 100644 --- a/cli/src/commands/flow/flow_metadata.ts +++ b/cli/src/commands/flow/flow_metadata.ts @@ -51,7 +51,12 @@ async function generateFlowHash( ); } } - return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; + // Sort keys so the top hash is deterministic regardless of filesystem readdir order + const sortedHashes: Record = {}; + for (const k of Object.keys(hashes).sort()) { + sortedHashes[k] = hashes[k]; + } + return { ...sortedHashes, [TOP_HASH]: await generateHash(JSON.stringify(sortedHashes)) }; } /** * Result of generating flow locks, including which scripts were updated diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index b672799106..84e6305ea9 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -2553,6 +2553,7 @@ export async function pull( ), ); } + } function prettyChanges( diff --git a/cli/src/utils/metadata.ts b/cli/src/utils/metadata.ts index f545dfa318..1ca8ce1f2f 100644 --- a/cli/src/utils/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -380,7 +380,10 @@ export async function generateScriptMetadataInternal( } } - const metadataContentUsedForHash = newMetadataContent; + // When justUpdateMetadataLock (sync pull), the metadata file is NOT rewritten, + // so use the raw file content for hashing to avoid YAML round-trip differences + // (e.g. hand-edited YAML that serializes differently after parse + stringify). + const metadataContentUsedForHash = justUpdateMetadataLock ? metadataContent : newMetadataContent; hash = await generateScriptHash( depsForHash,