@@ -1034,12 +1034,34 @@ func (js *jetStream) setupMetaGroup() error {
10341034 // If we are bootstrapped with no state, start campaign early.
10351035 if bootstrap {
10361036 n .Campaign ()
1037+ }
10371038
1038- // Write the PTC marker immediately on bootstrap so that a crash before
1039- // checkForOrphans runs does not lose the promotion-in-progress signal.
1040- // checkForOrphans clears the marker once there is nothing left to adopt.
1041- if err = os .WriteFile (filepath .Join (storeDir , ptcMarkerFile ), []byte {}, defaultFilePerms ); err != nil {
1042- s .Fatalf ("Failed to write PTC marker on bootstrap: %v" , err )
1039+ // Conditions for a genuine standalone promotion:
1040+ // 1. bootstrap=true: no peer state = this node was never in a cluster
1041+ // (a node recovering from cluster membership has peer state, so bootstrap=false)
1042+ // 2. No PTC marker yet: not resuming a prior partial adoption
1043+ // 3. Local JetStream accounts have streams: the server had running standalone data
1044+ //
1045+ // Writing the marker here ensures ptc=true when js.cluster is created below,
1046+ // so processStreamAssignment takes the preservation path on any name conflict
1047+ // during the catchup snapshot or WAL replay flush (both happen after cluster init).
1048+ if bootstrap && ! hasPTCMarker (storeDir ) {
1049+ js .mu .RLock ()
1050+ var hasLocalStreams bool
1051+ for _ , jsa := range js .accounts {
1052+ jsa .mu .RLock ()
1053+ hasLocalStreams = len (jsa .streams ) > 0
1054+ jsa .mu .RUnlock ()
1055+ if hasLocalStreams {
1056+ break
1057+ }
1058+ }
1059+ js .mu .RUnlock ()
1060+ if hasLocalStreams {
1061+ s .Noticef ("JetStream standalone-to-cluster promotion detected: activating PTC mode" )
1062+ if err := js .writePTCMarker (); err != nil {
1063+ s .Warnf ("Failed to write PTC marker for standalone-to-cluster promotion: %v" , err )
1064+ }
10431065 }
10441066 }
10451067
@@ -1054,7 +1076,12 @@ func (js *jetStream) setupMetaGroup() error {
10541076 c : c ,
10551077 qch : make (chan struct {}),
10561078 stopped : make (chan struct {}),
1057- ptc : bootstrap || hasPTCMarker (storeDir ),
1079+ // ptc is true when the PTC marker file exists. The marker is written either:
1080+ // - here in setupMetaGroup when standalone-to-cluster promotion is first detected
1081+ // (bootstrap=true + local streams present + no prior marker), or
1082+ // - in checkForOrphans when conflicts are discovered.
1083+ // The marker is removed once all orphaned streams/consumers are adopted.
1084+ ptc : hasPTCMarker (storeDir ),
10581085 }
10591086 atomic .StoreInt32 (& js .clustered , 1 )
10601087 c .registerWithAccount (sysAcc )
0 commit comments