diff --git a/package.json b/package.json
index 7a09afca..6486e255 100644
--- a/package.json
+++ b/package.json
@@ -50,9 +50,9 @@
"node": ">=18.0.0"
},
"dependencies": {
- "@salesforce/core": "^8.31.2",
+ "@salesforce/core": "^8.32.5",
"@salesforce/kit": "^3.2.6",
- "@salesforce/source-deploy-retrieve": "^12.36.6",
+ "@salesforce/source-deploy-retrieve": "^12.37.2",
"@salesforce/ts-types": "^2.0.12",
"fast-xml-parser": "^5.5.7",
"graceful-fs": "^4.2.11",
diff --git a/src/shared/remote/remoteSourceTrackingService.ts b/src/shared/remote/remoteSourceTrackingService.ts
index c342eb9e..b4a54d87 100644
--- a/src/shared/remote/remoteSourceTrackingService.ts
+++ b/src/shared/remote/remoteSourceTrackingService.ts
@@ -318,7 +318,19 @@ export class RemoteSourceTrackingService {
queriedMembers.map((member) => {
// remove anything returned from the query list
const metadataKey = getMetadataKey(member.MemberType, member.MemberName);
- const deleted = outstandingSourceMembers.delete(metadataKey);
+ let deleted = outstandingSourceMembers.delete(metadataKey);
+ // SourceMember may store platform event (and other custom object) children without the entity suffix.
+ // e.g., "MyEvent.Field__c" instead of "MyEvent__e.Field__c"
+ if (!deleted && member.MemberName.includes('.')) {
+ const dotIndex = member.MemberName.indexOf('.');
+ const parent = member.MemberName.substring(0, dotIndex);
+ const child = member.MemberName.substring(dotIndex);
+ for (const suffix of ['__e', '__b', '__x', '__mdt']) {
+ const alternateKey = getMetadataKey(member.MemberType, `${parent}${suffix}${child}`);
+ deleted = outstandingSourceMembers.delete(alternateKey);
+ if (deleted) break;
+ }
+ }
if (!deleted) {
bonusTypes.add(metadataKey);
}
diff --git a/test/nuts/platformEventTracking.nut.ts b/test/nuts/platformEventTracking.nut.ts
new file mode 100644
index 00000000..b6b57612
--- /dev/null
+++ b/test/nuts/platformEventTracking.nut.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2026, Salesforce, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import path from 'node:path';
+import { TestSession } from '@salesforce/cli-plugins-testkit';
+import { expect } from 'chai';
+import { Lifecycle, Org, SfProject } from '@salesforce/core';
+import { ComponentSet } from '@salesforce/source-deploy-retrieve';
+import { SourceTracking } from '../../src/sourceTracking';
+
+describe('platform event source tracking after deploy (@W-21612413@)', () => {
+ let session: TestSession;
+ let stl: SourceTracking;
+ const warnings: string[] = [];
+
+ before(async () => {
+ session = await TestSession.create({
+ project: {
+ sourceDir: path.join('test', 'nuts', 'repros', 'platformEventTracking'),
+ },
+ scratchOrgs: [
+ {
+ config: path.join('config', 'project-scratch-def.json'),
+ duration: 1,
+ setDefault: true,
+ tracksSource: true,
+ },
+ ],
+ devhubAuthStrategy: 'AUTO',
+ });
+
+ const org = await Org.create({ aliasOrUsername: session.orgs.get('default')?.username });
+ const project = await SfProject.resolve(session.project.dir);
+ stl = await SourceTracking.create({ org, project });
+
+ Lifecycle.getInstance().onWarning((w) => {
+ warnings.push(w);
+ return Promise.resolve();
+ });
+ });
+
+ after(async () => {
+ await session?.clean();
+ });
+
+ it('deploys a platform event with a custom field and updates source tracking without timeout', async () => {
+ const componentSet = ComponentSet.fromSource(
+ path.join(session.project.dir, 'force-app', 'main', 'default', 'objects', 'TestEvent__e')
+ );
+
+ const deploy = await componentSet.deploy({ usernameOrConnection: session.orgs.get('default')!.username! });
+ const deployResult = await deploy.pollStatus();
+
+ expect(deployResult.response.success, 'deploy should succeed').to.equal(true);
+
+ const fileResponses = deployResult.getFileResponses();
+ expect(fileResponses.length).to.be.greaterThan(0);
+
+ await stl.updateTrackingFromDeploy(deployResult);
+
+ const pollingTimeoutWarnings = warnings.filter((w) => w.includes('SourceMembers timed out'));
+ expect(pollingTimeoutWarnings, 'source tracking should not time out polling for SourceMembers').to.have.length(0);
+ });
+});
diff --git a/test/nuts/repros/platformEventTracking/config/project-scratch-def.json b/test/nuts/repros/platformEventTracking/config/project-scratch-def.json
new file mode 100644
index 00000000..fbfcc05f
--- /dev/null
+++ b/test/nuts/repros/platformEventTracking/config/project-scratch-def.json
@@ -0,0 +1,6 @@
+{
+ "orgName": "Platform Event Tracking Test",
+ "edition": "Developer",
+ "adminEmail": "nut@mailinator.com",
+ "hasSampleData": false
+}
diff --git a/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/TestEvent__e.object-meta.xml b/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/TestEvent__e.object-meta.xml
new file mode 100644
index 00000000..98dcd567
--- /dev/null
+++ b/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/TestEvent__e.object-meta.xml
@@ -0,0 +1,7 @@
+
+
+ Deployed
+ HighVolume
+
+ Test Events
+
diff --git a/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/fields/Message__c.field-meta.xml b/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/fields/Message__c.field-meta.xml
new file mode 100644
index 00000000..8ea75813
--- /dev/null
+++ b/test/nuts/repros/platformEventTracking/force-app/main/default/objects/TestEvent__e/fields/Message__c.field-meta.xml
@@ -0,0 +1,10 @@
+
+
+ Message__c
+ false
+
+ 255
+ false
+ Text
+ false
+
diff --git a/test/nuts/repros/platformEventTracking/sfdx-project.json b/test/nuts/repros/platformEventTracking/sfdx-project.json
new file mode 100644
index 00000000..1c19a57b
--- /dev/null
+++ b/test/nuts/repros/platformEventTracking/sfdx-project.json
@@ -0,0 +1,6 @@
+{
+ "packageDirectories": [{ "path": "force-app", "default": true }],
+ "namespace": "",
+ "sfdcLoginUrl": "https://login.salesforce.com",
+ "sourceApiVersion": "62.0"
+}
diff --git a/test/unit/remote/expectedSourceMembers.test.ts b/test/unit/remote/expectedSourceMembers.test.ts
index 9139a446..7ff11b48 100644
--- a/test/unit/remote/expectedSourceMembers.test.ts
+++ b/test/unit/remote/expectedSourceMembers.test.ts
@@ -146,4 +146,17 @@ describe('expectedSourceMembers', () => {
expect(result.size).to.equal(1);
expect(result.has('ApexClass###MyClass')).to.equal(true);
});
+
+ it('includes custom fields on platform events (__e)', () => {
+ const input = [
+ {
+ type: 'CustomField',
+ fullName: 'MyEvent__e.SomeField__c',
+ filePath: 'src/objects/MyEvent__e/fields/SomeField__c.field-meta.xml',
+ state: ComponentStatus.Created,
+ },
+ ];
+ const result = calculateExpectedSourceMembers(registry, input);
+ expect(result.has('CustomField###MyEvent__e.SomeField__c')).to.equal(true);
+ });
});
diff --git a/test/unit/remote/remoteSourceTracking.test.ts b/test/unit/remote/remoteSourceTracking.test.ts
index cd1f833c..39ef9162 100644
--- a/test/unit/remote/remoteSourceTracking.test.ts
+++ b/test/unit/remote/remoteSourceTracking.test.ts
@@ -614,6 +614,72 @@ describe('remoteSourceTrackingService', () => {
).to.equal(true);
expect(queryStub.called).to.equal(true);
});
+
+ it('should match SourceMembers when server strips __e suffix from platform event parent', async () => {
+ const platformEventMembers: RemoteSyncInput[] = [
+ {
+ type: 'CustomField',
+ fullName: 'MyEvent__e.SomeField__c',
+ filePath: 'src/objects/MyEvent__e/fields/SomeField__c.field-meta.xml',
+ state: ComponentStatus.Changed,
+ },
+ ];
+
+ const queryStub = $$.SANDBOX.stub(orgQueryMocks, 'querySourceMembersFrom');
+ // @ts-expect-error it's private
+ remoteSourceTrackingService.serverMaxRevisionCounter = 9;
+
+ // Server returns the member WITHOUT __e suffix (the bug scenario)
+ queryStub.onFirstCall().resolves([
+ {
+ ...defaultSourceMemberValues,
+ RevisionCounter: 10,
+ MemberType: 'CustomField',
+ MemberName: 'MyEvent.SomeField__c',
+ IsNameObsolete: false,
+ },
+ ]);
+
+ // @ts-expect-error stubbing private method for testing
+ const trackSpy = $$.SANDBOX.stub(remoteSourceTrackingService, 'trackSourceMembers');
+
+ await remoteSourceTrackingService.pollForSourceTracking(new RegistryAccess(), platformEventMembers);
+ expect(trackSpy.calledOnce).to.equal(true);
+ expect(queryStub.calledOnce).to.equal(true);
+ });
+
+ it('should match SourceMembers when server strips __mdt suffix from custom metadata parent', async () => {
+ const mdtMembers: RemoteSyncInput[] = [
+ {
+ type: 'CustomField',
+ fullName: 'MyConfig__mdt.Value__c',
+ filePath: 'src/objects/MyConfig__mdt/fields/Value__c.field-meta.xml',
+ state: ComponentStatus.Changed,
+ },
+ ];
+
+ const queryStub = $$.SANDBOX.stub(orgQueryMocks, 'querySourceMembersFrom');
+ // @ts-expect-error it's private
+ remoteSourceTrackingService.serverMaxRevisionCounter = 9;
+
+ queryStub.onFirstCall().resolves([
+ {
+ ...defaultSourceMemberValues,
+ RevisionCounter: 10,
+ MemberType: 'CustomField',
+ MemberName: 'MyConfig.Value__c',
+ IsNameObsolete: false,
+ },
+ ]);
+
+ // @ts-expect-error stubbing private method for testing
+ const trackSpy = $$.SANDBOX.stub(remoteSourceTrackingService, 'trackSourceMembers');
+
+ await remoteSourceTrackingService.pollForSourceTracking(new RegistryAccess(), mdtMembers);
+ expect(trackSpy.calledOnce).to.equal(true);
+ expect(queryStub.calledOnce).to.equal(true);
+ });
+
it('should sync specific elements', async () => {
expect(getContents()).to.deep.equal({
serverMaxRevisionCounter: 0,
diff --git a/yarn.lock b/yarn.lock
index 63b8cee7..b2494a3a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -586,6 +586,21 @@
node-fetch "^2.6.1"
xml2js "^0.6.2"
+"@jsforce/jsforce-node@^3.10.17":
+ version "3.10.19"
+ resolved "https://registry.yarnpkg.com/@jsforce/jsforce-node/-/jsforce-node-3.10.19.tgz#ccbc539c12f4f7dff9cfdcc6cfb8f07bd840f731"
+ integrity sha512-k7i2Tntu1fLvkMtRcKDFU64/Fr2M692ECtbwIGX6hcOh5mj+jrMa1tlvcdwffxAMl+lPYCXnY2bjErxWmP84zA==
+ dependencies:
+ "@sindresorhus/is" "^4"
+ base64url "^3.0.1"
+ csv-parse "^5.5.2"
+ csv-stringify "^6.6.0"
+ faye "^1.4.0"
+ form-data "^4.0.4"
+ multistream "^3.1.0"
+ undici "^8.5.0"
+ xml2js "^0.6.2"
+
"@jsonjoy.com/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz"
@@ -706,12 +721,12 @@
ts-retry-promise "^0.8.1"
zod "^4.1.12"
-"@salesforce/core@^8.31.2":
- version "8.31.2"
- resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.31.2.tgz#968448f423b553f726f42c27da6b55c4ec7eb93a"
- integrity sha512-naqnq7Z+gbl1LdnyNvrGrNUoeMUQtCOsnrS6DfqeuLMJTFqcL9Dq0/od+xcuqi0+l7HTyH0/gU1BQitWpd1rag==
+"@salesforce/core@^8.32.2", "@salesforce/core@^8.32.5":
+ version "8.32.5"
+ resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.32.5.tgz#e03eb5786a5cd2b8578c1906ee60a83ea7e70b9f"
+ integrity sha512-8me3JSwUaEkQ9oy6PdmddX6OAA8krESKMmr26HPD0zhuef00vt0Cax1fju2DGDn50LRQh3nGkC/qr/mGMYSXuA==
dependencies:
- "@jsforce/jsforce-node" "^3.10.13"
+ "@jsforce/jsforce-node" "^3.10.17"
"@salesforce/kit" "^3.2.4"
"@salesforce/ts-types" "^2.0.12"
ajv "^8.18.0"
@@ -792,12 +807,12 @@
resolved "https://registry.yarnpkg.com/@salesforce/schemas/-/schemas-1.10.3.tgz#52c867fdd60679cf216110aa49542b7ad391f5d1"
integrity sha512-FKfvtrYTcvTXE9advzS25/DEY9yJhEyLvStm++eQFtnAaX1pe4G3oGHgiQ0q55BM5+0AlCh0+0CVtQv1t4oJRA==
-"@salesforce/source-deploy-retrieve@^12.36.6":
- version "12.36.6"
- resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.36.6.tgz#4c1e701cff9c9fa2802e8fa7e3ba2188426d47d1"
- integrity sha512-1tz3eUyHp0yIAylrz+njlxGejd08cwPys9LjfBTVeY6a+xOBkTTDgQMLA6OBBZVc0HMPqjkJMKvbtDrAD5MJBA==
+"@salesforce/source-deploy-retrieve@^12.37.2":
+ version "12.37.2"
+ resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.37.2.tgz#442f3be88d91021de14af6371d9f954689157ce7"
+ integrity sha512-wgG0RccjQ8CaBEO8woSvvTyxc6V7KIw1GbZlME0bU+Ly+2G8pdPHAm43hrBTBml5MdYVh3J3yZwMyIeRaQxeBA==
dependencies:
- "@salesforce/core" "^8.31.2"
+ "@salesforce/core" "^8.32.2"
"@salesforce/kit" "^3.2.4"
"@salesforce/ts-types" "^2.0.12"
"@salesforce/types" "^1.6.0"
@@ -2936,17 +2951,6 @@ form-data@^4.0.4, form-data@^4.0.5:
hasown "^2.0.4"
mime-types "^2.1.35"
-form-data@^4.0.5:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827"
- integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.8"
- es-set-tostringtag "^2.1.0"
- hasown "^2.0.4"
- mime-types "^2.1.35"
-
fromentries@^1.2.0:
version "1.3.2"
resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz"
@@ -6282,6 +6286,11 @@ undici-types@~6.21.0:
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz"
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
+undici@^8.5.0:
+ version "8.8.0"
+ resolved "https://registry.yarnpkg.com/undici/-/undici-8.8.0.tgz#5e858505ed78074c6b31b0fb7cce57ad56cc6267"
+ integrity sha512-ubshXMXwF3MQIMF1y/WxZdNBnjEKeSg2wF5mcGUtU55YTw34tnVVpKRlLf7ruDXZ5344KokPVX4RBx1wJm64Bw==
+
unist-util-is@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz"