diff --git a/.github/workflows/update-version.yml b/.github/workflows/update-version.yml index 04416c9a..5df78a90 100644 --- a/.github/workflows/update-version.yml +++ b/.github/workflows/update-version.yml @@ -15,6 +15,9 @@ on: permissions: contents: write pull-requests: write + # Note: Auto-merge may require additional permissions depending on repository settings. + # If auto-merge fails, consider using a Personal Access Token (PAT) or GitHub App token + # with 'pull-requests: write' and 'contents: write' scopes. jobs: update-version: @@ -145,23 +148,44 @@ jobs: > **Note:** This PR was created with force mode enabled." fi - # Create PR - PR_URL=$(gh pr create \ + # Create PR with JSON output for reliable success detection + PR_RESULT=$(gh pr create \ --title "chore: update WhatsApp Web version" \ --body "$PR_BODY" \ --base master \ - --head "$BRANCH_NAME" 2>&1) || true + --head "$BRANCH_NAME" \ + --json url,number 2>&1) || PR_CREATE_FAILED=true - if [[ "$PR_URL" == *"github.com"* ]]; then - echo "PR created: $PR_URL" + # Check if PR was created successfully by parsing JSON + if [ -z "$PR_CREATE_FAILED" ] && echo "$PR_RESULT" | jq -e '.url' > /dev/null 2>&1; then + PR_URL=$(echo "$PR_RESULT" | jq -r '.url') + PR_NUMBER=$(echo "$PR_RESULT" | jq -r '.number') + echo "PR created successfully: $PR_URL (PR #$PR_NUMBER)" # Auto-merge the PR (squash merge) - echo "Attempting to auto-merge..." - gh pr merge "$BRANCH_NAME" --squash --auto --delete-branch || \ - gh pr merge "$BRANCH_NAME" --squash --delete-branch || \ - echo "Auto-merge not available - PR created for manual review" + # Note: This requires branch protection rules to be satisfied + # If checks haven't completed yet, auto-merge will queue the merge + echo "Attempting to enable auto-merge..." + if gh pr merge "$BRANCH_NAME" --squash --auto --delete-branch; then + echo "✅ Auto-merge enabled - PR will merge automatically when checks pass" + else + echo "⚠️ Auto-merge not available - PR #$PR_NUMBER created for manual review" + echo "This may happen if:" + echo " - Required status checks haven't completed yet" + echo " - Branch protection rules require manual approval" + echo " - Insufficient permissions (see workflow permissions comment)" + fi else - echo "PR already exists or could not be created: $PR_URL" + # PR creation failed - could be duplicate or other error + echo "⚠️ Could not create PR. Checking if PR already exists..." + EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --json number,url --jq '.[0]') + if [ -n "$EXISTING_PR" ]; then + EXISTING_URL=$(echo "$EXISTING_PR" | jq -r '.url') + EXISTING_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number') + echo "PR already exists: $EXISTING_URL (PR #$EXISTING_NUMBER)" + else + echo "❌ PR creation failed: $PR_RESULT" + fi fi - name: Summary diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index d8a63368..f7038bbd 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -1216,33 +1216,42 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } = decryptMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '', signalRepository, logger) const alt = msg.key.participantAlt || msg.key.remoteJidAlt - // Store LID/PN mappings asynchronously (fire-and-forget) to avoid blocking message delivery - // This improves inbound message latency by moving non-critical operations to background + // Handle LID/PN mappings with hybrid approach: + // - Store mapping operation runs in background (non-critical for decrypt) + // - Session migration MUST complete before decrypt() to avoid "No session record" errors + // This addresses Codex/Copilot review concerns about race conditions with decrypt() if (!!alt) { const altServer = jidDecode(alt)?.server const primaryJid = msg.key.participant || msg.key.remoteJid! - // Execute mapping operations in background without blocking message delivery - const processMappingAsync = async () => { - try { - if (altServer === 'lid') { - if (!(await signalRepository.lidMapping.getPNForLID(alt))) { - await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]) - await signalRepository.migrateSession(primaryJid, alt) - } - } else { - await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]) - await signalRepository.migrateSession(alt, primaryJid) - } - } catch (error) { - logger.warn({ error, alt, primaryJid }, 'Background LID mapping operation failed') + if (altServer === 'lid') { + // Check if mapping already exists to avoid unnecessary storage operations + const existingMapping = await signalRepository.lidMapping.getPNForLID(alt) + if (!existingMapping) { + // Store mapping in background (non-critical, doesn't block decrypt) + signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]) + .catch(error => logger.warn({ error, alt, primaryJid }, 'Background LID mapping storage failed')) } - } - // Fire and forget - don't await - processMappingAsync().catch(error => { - logger.error({ error, alt, primaryJid }, 'Fatal error in background LID mapping') - }) + // CRITICAL: ALWAYS migrate session, even if mapping exists + // Other code paths (e.g., USync device lookup in messages-send.ts:310-319) + // may create mappings via storeLIDPNMappings() without calling migrateSession() + // This leaves sessions under PN format while decrypt() expects LID format + // Skipping migration based on mapping existence causes "No session record" errors + await signalRepository.migrateSession(primaryJid, alt) + } else { + // Check if reverse mapping exists + const existingMapping = await signalRepository.lidMapping.getLIDForPN(alt) + if (!existingMapping) { + // Store mapping in background (non-critical) + signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]) + .catch(error => logger.warn({ error, alt, primaryJid }, 'Background LID mapping storage failed')) + } + + // CRITICAL: ALWAYS migrate session, even if mapping exists + // Same reasoning as above - mapping existence doesn't guarantee session migration + await signalRepository.migrateSession(alt, primaryJid) + } } if (msg.key?.remoteJid && msg.key?.id && messageRetryManager) { diff --git a/src/Utils/process-message.ts b/src/Utils/process-message.ts index 7cdc16a0..2c45341a 100644 --- a/src/Utils/process-message.ts +++ b/src/Utils/process-message.ts @@ -131,12 +131,16 @@ export const normalizeMessageJids = async ( return jid } - const resolvedRemoteJid = await resolveLidToPn(message.key.remoteJid) + // Execute both lookups in parallel instead of sequentially to reduce latency + const [resolvedRemoteJid, resolvedParticipant] = await Promise.all([ + resolveLidToPn(message.key.remoteJid), + resolveLidToPn(message.key.participant) + ]) + if (resolvedRemoteJid) { message.key.remoteJid = resolvedRemoteJid } - const resolvedParticipant = await resolveLidToPn(message.key.participant) if (resolvedParticipant) { message.key.participant = resolvedParticipant }