diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0e7501b..8144b79b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to
- ♻️(frontend) encapsulate error tracking behind a telemetry module
- ♻️(frontend) encapsulate PostHog capture calls in the telemetry module
+- 🔧(frontend) sync persisted device ids with the actual selected devices
### Fixed
diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx
index 5bc75070..3d83c767 100644
--- a/src/frontend/src/features/rooms/components/Join.tsx
+++ b/src/frontend/src/features/rooms/components/Join.tsx
@@ -55,6 +55,7 @@ import {
import { saveUsername, userStore } from '@/stores/user'
import { useCannotUseDevice } from '../livekit/hooks/useCannotUseDevice'
+import { useSyncTrackDeviceId } from '../livekit/hooks/useSyncTrackDeviceId'
import { useSnapshot } from 'valtio'
import { useUser } from '@/features/auth/api/useUser'
import { useConfig } from '@/api/useConfig'
@@ -274,6 +275,8 @@ export const Join = ({
const videoTrack = dynamicVideoTrack || previewVideoTrack
const audioTrack = dynamicAudioTrack || previewAudioTrack
+ useSyncTrackDeviceId(audioTrack, saveAudioInputDeviceId)
+ useSyncTrackDeviceId(videoTrack, saveVideoInputDeviceId)
const videoEl = useRef(null)
const isVideoInitiated = useRef(false)
diff --git a/src/frontend/src/features/rooms/livekit/components/SyncDevicePreferences.tsx b/src/frontend/src/features/rooms/livekit/components/SyncDevicePreferences.tsx
new file mode 100644
index 00000000..ba02c17d
--- /dev/null
+++ b/src/frontend/src/features/rooms/livekit/components/SyncDevicePreferences.tsx
@@ -0,0 +1,20 @@
+import { useLocalParticipant } from '@livekit/components-react'
+import type { LocalTrack } from 'livekit-client'
+import { useSyncTrackDeviceId } from '../hooks/useSyncTrackDeviceId'
+import {
+ saveAudioInputDeviceId,
+ saveVideoInputDeviceId,
+} from '@/stores/userChoices'
+
+export const SyncDevicePreferences = () => {
+ const { cameraTrack, microphoneTrack } = useLocalParticipant()
+ useSyncTrackDeviceId(
+ cameraTrack?.track as LocalTrack | undefined,
+ saveVideoInputDeviceId
+ )
+ useSyncTrackDeviceId(
+ microphoneTrack?.track as LocalTrack | undefined,
+ saveAudioInputDeviceId
+ )
+ return null
+}
diff --git a/src/frontend/src/features/rooms/livekit/hooks/useSyncTrackDeviceId.ts b/src/frontend/src/features/rooms/livekit/hooks/useSyncTrackDeviceId.ts
new file mode 100644
index 00000000..7ce924b9
--- /dev/null
+++ b/src/frontend/src/features/rooms/livekit/hooks/useSyncTrackDeviceId.ts
@@ -0,0 +1,40 @@
+import { useEffect } from 'react'
+import { TrackEvent, type LocalTrack } from 'livekit-client'
+
+/**
+ * Keeps the persisted preference aligned with the track's actual device.
+ * The track is the source of truth, not the store.
+ *
+ * Syncs on mount and TrackEvent.Restarted, covering acquisition/restart,
+ * setDeviceId switches, unmute re-acquisition, and browser fallback.
+ *
+ * Skip persisting the raw "default" alias: it means "follow the OS default"
+ * and resolving it would pin the preference to a concrete device.
+ */
+export const useSyncTrackDeviceId = (
+ track: LocalTrack | undefined,
+ save: (deviceId: string) => void
+) => {
+ useEffect(() => {
+ if (!track) return
+ let cancelled = false
+ const sync = () => {
+ track
+ .getDeviceId(false)
+ .then((deviceId) => {
+ if (!cancelled && deviceId) {
+ save(deviceId)
+ }
+ })
+ .catch(() => {
+ // A track without settings (ended, screen share) has no id to sync.
+ })
+ }
+ sync()
+ track.on(TrackEvent.Restarted, sync)
+ return () => {
+ cancelled = true
+ track.off(TrackEvent.Restarted, sync)
+ }
+ }, [track, save])
+}
diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
index e6bfe693..1859fff3 100644
--- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
+++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
@@ -26,6 +26,7 @@ import { PipRoomPlaceholder } from '@/features/pip/components/PipRoomPlaceholder
import { StageLayout } from '@/features/layout/components/StageLayout'
import { PinAnnouncer } from '@/features/layout/components/PinAnnouncer'
import { ChatProvider } from '@/features/chat/components/ChatProvider'
+import { SyncDevicePreferences } from '@/features/rooms/livekit/components/SyncDevicePreferences'
/**
* @public
@@ -65,6 +66,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
<>
+