{"record":{"id":"68186440bdf13072","repo":"stablyai/orca","slug":"couldn-t-verify-the-ssh-connection-reconnect-the","errorCode":null,"errorMessage":"Couldn't verify the SSH connection. Reconnect the host and try again.","messagePattern":"Couldn't verify the SSH connection\\. Reconnect the host and try again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/files/mobile-file-mutation-ownership.ts","lineNumber":22,"sourceCode":"import type { SshConnectionState, SshMutationExpectation } from '../../../src/shared/ssh-types'\nimport type { RpcClient } from '../transport/rpc-client'\nimport type { RpcFailure, RpcSuccess } from '../transport/types'\n\nconst FILE_MUTATION_TIMEOUT_MS = 15_000\nconst SSH_OWNER_CHANGED_MESSAGE =\n  \"Couldn't verify the SSH connection. Reconnect the host and try again.\"\n\nexport type MobileFileMutationOwnership = SshMutationExpectation & {\n  expectedExecutionHostId: 'local' | `ssh:${string}`\n}\n\nexport function buildMobileFileMutationOwnership(\n  worktreeHostId: string | null | undefined,\n  sshState: SshConnectionState | null = null\n): MobileFileMutationOwnership {\n  const host = parseExecutionHostId(worktreeHostId)\n  if (worktreeHostId !== undefined && !host) {\n    throw new Error(SSH_OWNER_CHANGED_MESSAGE)\n  }\n  if (!host || host.kind === 'local' || host.kind === 'runtime') {\n    return { expectedExecutionHostId: 'local' }\n  }\n  if (sshState?.targetId !== host.targetId || sshState.connectionGeneration === undefined) {\n    throw new Error(SSH_OWNER_CHANGED_MESSAGE)\n  }\n  return {\n    expectedExecutionHostId: host.id,\n    expectedSshTargetId: host.targetId,\n    expectedSshConnectionGeneration: sshState.connectionGeneration\n  }\n}\n\nexport async function captureMobileFileMutationOwnership(\n  client: Pick<RpcClient, 'sendRequest'>,\n  worktree: string\n): Promise<MobileFileMutationOwnership> {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/files/mobile-file-mutation-ownership.ts#L4-L40","documentation":"Thrown by buildMobileFileMutationOwnership when worktreeHostId is a defined non-undefined value but parseExecutionHostId could not parse it into a recognized host (local | runtime | ssh). This guards against committing a file mutation to a host whose execution identity the client cannot interpret — writing through an ambiguous host id would risk mutating the wrong machine.","triggerScenarios":"buildMobileFileMutationOwnership(worktreeHostId, sshState) is called with worktreeHostId that is defined (not undefined) but not 'local', not a 'runtime:...' id, and not an 'ssh:...' id. Concretely: a malformed host string from worktree.show, a new host-kind prefix the client build does not yet understand, or a corrupted persisted host id.","commonSituations":"A newer desktop build introduces a new execution-host kind (e.g., 'container:...') that this mobile build's parseExecutionHostId does not recognize; a worktree.show result with a truncated or corrupt hostId; an old mobile app paired with a newer desktop that emits a different host id scheme.","solutions":["Update the mobile app so parseExecutionHostId understands the new host-kind prefix.","If worktreeHostId came from worktree.show, re-fetch it — a corrupt value often indicates a stale worktree row; rotate the worktree.","Treat as 'reconnect the host': the SSH_OWNER_CHANGED_MESSAGE tells the user to re-establish the host connection so a fresh, parseable hostId is captured.","If the hostId is genuinely a new kind and updating is not possible, block mutations on that worktree until the app is upgraded."],"exampleFix":"// before\nconst host = parseExecutionHostId(worktreeHostId)\nif (worktreeHostId !== undefined && !host) {\n  throw new Error(SSH_OWNER_CHANGED_MESSAGE)\n}\n\n// after — distinguish 'new kind' from 'corrupt' for clearer UX\nconst host = parseExecutionHostId(worktreeHostId)\nif (worktreeHostId !== undefined && !host) {\n  if (typeof worktreeHostId === 'string' && worktreeHostId.includes(':')) {\n    throw new Error(`Unsupported host kind: ${worktreeHostId.split(':')[0]}. Update the app.`)\n  }\n  throw new Error(SSH_OWNER_CHANGED_MESSAGE)\n}","handlingStrategy":"validation","validationCode":"// Validate the host id is parseable before building ownership\nimport { parseExecutionHostId } from '../../../src/shared/execution-host'\nif (worktreeHostId !== undefined && !parseExecutionHostId(worktreeHostId)) {\n  throw new Error('Cannot mutate files: unrecognized host id. Reconnect the host.')\n}","typeGuard":"function isRecognizedHostId(id: string | null | undefined): boolean {\n  if (id === undefined || id === null) return true\n  return parseExecutionHostId(id) !== null\n}","tryCatchPattern":"try {\n  return buildMobileFileMutationOwnership(worktreeHostId, sshState)\n} catch (err) {\n  if (err instanceof Error && err.message === SSH_OWNER_CHANGED_MESSAGE) {\n    await forceReconnect()\n    const fresh = await captureMobileFileMutationOwnership(client, worktree)\n    return fresh\n  }\n  throw err\n}","preventionTips":["Treat unparseable host ids as a hard stop — never fall back to 'local'.","When a new host-kind prefix is introduced, update parseExecutionHostId in lockstep.","Re-capture ownership via captureMobileFileMutationOwnership after any host reconnect."],"tags":["ssh","execution-host","validation","file-mutation","typescript"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}