Change:
I had to clear settings sync for an unrelated issue and start fresh, but enabling the "UI State" option causes the error below to be continually thrown. If I disable the "UI State" option in settings sync, it starts working again. Type: <bBug</bsettings sync, check all boxes except "UI State" settings sync log, see that it succeeds settings sync config to include UI State VS Code version: Code 1.85.1 (Universal) (, 2023-12-13T09:48:06.308Z) OS version: Darwin arm64 23.2.0 Modes: <details<summarySystem Info</summary </details<details<summaryExtensions (12)</summaryExtensionVersion ------ erb0.0.1 vscode-eslint2.4.2 prettier-vscode10.1.0 codespaces1.16.6 copilot1.148.0 copilot-chat0.11.1 vscode-graphql-syntax1.2.3 endwise1.5.1 remote-containers0.327.0 proto0.0.4 rewrap1.16.3 gitblame10.6.0 (1 theme extensions excluded) </details<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --
Can you please share the UI state using following steps F1 Export Profile... Select only UI state Export the profile and share it
The exported file is 2.3 MB, and it has some things inside that might be considered sensitive, I'm not sure. Is there a way I can share it privately?
You can share it to me by email - 2.3 MB is way under the limits right?
Just emailed the file, thank you!
I just checked the size limits on the server and it is indeed 2MB caps
Thanks for sharing. I figured out that port forwarding state is taking majority of the size. Hence assigning to
Discussed with and there are 3 easy things I can do to quickly reduce the size: Don't save empty values Add an expiry time to values so they don't live forever Save fewer fields per port in . For example, only tunnels should be stored + restored, so should be removed. So should , , , and .
the limit per resource version is 2mb atm
Are there steps to verify this?
To verify: to a Codespace and forward a port the remote connection the steps in to collect a profile the file created in step 3. Check that the port that you just forwarded in step 1 is listed there under a key and that it doesn't have a value.
For the step, does this mean to stop the codespace?
This is verified. The new port forwarded doesn't have , but I did notice some old ports of mine from old codespaces still having . Not sure if that was expected to be covered.
The behavior you see is expected. I didn't add any cleanup of the old state.

Discussion:
const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000; // 10 seconds const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; <ins> const TUNNELS_TO_RESTORE_EXPIRATION = 'remote.tunnels.toRestoreExpiration'; const RESTORE_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14; // 2 weeks </ins> export const ACTIVATION_EVENT = 'onTunnel'; export const forwardedPortsViewEnabled = new RawContextKey<boolean>('forwardedPortsViewEnabled', false, nls.localize('tunnel.forwardedPortsViewEnabled', "Whether the Ports view is enabled.")); <ins> export interface RestorableTunnel { remoteHost: string; remotePort: number; localAddress: string; localUri: URI; protocol: TunnelProtocol; localPort?: number; name?: string; source: { source: TunnelSource; description: string; }; } </ins> export interface Tunnel { remoteHost: string; remotePort: number;

private knownPortsRestoreValue: string | undefined; private restoreComplete = false; private onRestoreComplete: Emitter<void> = new Emitter(); <del> private unrestoredExtensionTunnels: Map<string, Tunnel> = new Map(); </del> <ins> private unrestoredExtensionTunnels: Map<string, RestorableTunnel> = new Map(); </ins> private sessionCachedProperties: Map<string, Partial<TunnelProperties>> = new Map(); private portAttributesProviders: PortAttributesProvider[] = [];

return URI.parse(`${protocol}://${localAddress}`); } <del> private async getStorageKey(): Promise<string | undefined> { </del> <ins> private async addStorageKeyPostfix(prefix: string): Promise<string | undefined> { </ins> const workspace = this.workspaceContextService.getWorkspace(); const workspaceHash = workspace.configuration ? hash(workspace.configuration.path) : (workspace.folders.length > 0 ? hash(workspace.folders[0].uri.path) : undefined); if (workspaceHash === undefined) { this.logService.debug('Could not get workspace hash for forwarded ports storage key.'); return undefined; } <del> return `${TUNNELS_TO_RESTORE}.${this.environmentService.remoteAuthority}.${workspaceHash}`; </del> <ins> return `${prefix}.${this.environmentService.remoteAuthority}.${workspaceHash}`; } private async getTunnelRestoreStorageKey(): Promise<string | undefined> { return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE); } private async getRestoreExpirationStorageKey(): Promise<string | undefined> { return this.addStorageKeyPostfix(TUNNELS_TO_RESTORE_EXPIRATION); </ins> } private async getTunnelRestoreValue(): Promise<string | undefined> {

await this.storeForwarded(); return deprecatedValue; } <del> const storageKey = await this.getStorageKey(); </del> <ins> const storageKey = await this.getTunnelRestoreStorageKey(); </ins> if (!storageKey) { return undefined; }

} async restoreForwarded() { <ins> this.cleanupExpiredTunnelsForRestore(); </ins> if (this.configurationService.getValue('remote.restoreForwardedPorts')) { const tunnelRestoreValue = await this.tunnelRestoreValue; if (tunnelRestoreValue && (tunnelRestoreValue !== this.knownPortsRestoreValue)) { <del> const tunnels = <Tunnel[] | undefined>JSON.parse(tunnelRestoreValue) ?? []; </del> <ins> const tunnels = <RestorableTunnel[] | undefined>JSON.parse(tunnelRestoreValue) ?? []; </ins> this.logService.trace(`ForwardedPorts: (TunnelModel) restoring ports ${tunnels.map(tunnel => tunnel.remotePort).join(', ')}`); for (const tunnel of tunnels) { const alreadyForwarded = mapHasAddressLocalhostOrAllInterfaces(this.detected, tunnel.remoteHost, tunnel.remotePort);

remote: { host: tunnel.remoteHost, port: tunnel.remotePort }, local: tunnel.localPort, name: tunnel.name, <del> privacy: tunnel.privacy, </del> elevateIfNeeded: true, source: tunnel.source });

if (!this.restoreListener) { // It's possible that at restore time the value hasn't synced. <del> const key = await this.getStorageKey(); </del> <ins> const key = await this.getTunnelRestoreStorageKey(); </ins> this.restoreListener = this._register(new DisposableStore()); this.restoreListener.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this.restoreListener)(async (e) => { if (e.key === key) {

} } <ins> private cleanupExpiredTunnelsForRestore() { const keys = this.storageService.keys(StorageScope.PROFILE, StorageTarget.USER).filter(key => key.startsWith(TUNNELS_TO_RESTORE_EXPIRATION)); for (const key of keys) { const expiration = this.storageService.getNumber(key, StorageScope.PROFILE); if (expiration && expiration < Date.now()) { this.tunnelRestoreValue = Promise.resolve(undefined); const storageKey = key.replace(TUNNELS_TO_RESTORE_EXPIRATION, TUNNELS_TO_RESTORE); this.storageService.remove(key, StorageScope.PROFILE); this.storageService.remove(storageKey, StorageScope.PROFILE); } } } </ins> @debounce(1000) private async storeForwarded() { if (this.configurationService.getValue('remote.restoreForwardedPorts')) { <del> const valueToStore = JSON.stringify(Array.from(this.forwarded.values())); if (valueToStore !== this.knownPortsRestoreValue) { this.knownPortsRestoreValue = valueToStore; const key = await this.getStorageKey(); if (key) { this.storageService.store(key, this.knownPortsRestoreValue, StorageScope.PROFILE, StorageTarget.USER); } </del> <ins> const forwarded = Array.from(this.forwarded.values()); const restorableTunnels: RestorableTunnel[] = forwarded.map(tunnel => { return { remoteHost: tunnel.remoteHost, remotePort: tunnel.remotePort, localPort: tunnel.localPort, name: tunnel.name, localAddress: tunnel.localAddress, localUri: tunnel.localUri, protocol: tunnel.protocol, source: tunnel.source, }; }); let valueToStore: string | undefined; if (forwarded.length > 0) { valueToStore = JSON.stringify(restorableTunnels); } const key = await this.getTunnelRestoreStorageKey(); const expirationKey = await this.getRestoreExpirationStorageKey(); if (!valueToStore && key && expirationKey) { this.storageService.remove(key, StorageScope.PROFILE); this.storageService.remove(expirationKey, StorageScope.PROFILE); } else if ((valueToStore !== this.knownPortsRestoreValue) && key && expirationKey) { this.storageService.store(key, valueToStore, StorageScope.PROFILE, StorageTarget.USER); this.storageService.store(expirationKey, Date.now() + RESTORE_EXPIRATION_TIME, StorageScope.PROFILE, StorageTarget.USER); </ins> } <ins> this.knownPortsRestoreValue = valueToStore; </ins> } }

if (updateProps) { tunnelProperties.name = updateProps.name ?? tunnelProperties.name; tunnelProperties.local = (('local' in updateProps) ? updateProps.local : (('localPort' in updateProps) ? updateProps.localPort : undefined)) ?? tunnelProperties.local; <del> tunnelProperties.privacy = updateProps.privacy ?? tunnelProperties.privacy; </del> <ins> tunnelProperties.privacy = tunnelProperties.privacy; </ins> } } return tunnelProperties;