{"record":{"id":"f6513728abc89851","repo":"slopus/happy","slug":"ambiguous-happy-session-trimmed-matches-mat","errorCode":null,"errorMessage":"Ambiguous Happy session \"${trimmed}\" matches ${matches.length} sessions. Be more specific.","messagePattern":"Ambiguous Happy session \"(.+?)\" matches (.+?) sessions\\. Be more specific\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/resume/resolveHappySession.ts","lineNumber":70,"sourceCode":"    seq: number;\n    metadataVersion: number;\n    agentStateVersion: number;\n    encryptionKey: Uint8Array;\n    encryptionVariant: 'legacy' | 'dataKey';\n};\n\nexport function resolveSessionRecordByPrefix<T extends { id: string }>(records: T[], sessionId: string): T {\n    const trimmed = sessionId.trim();\n    if (!trimmed) {\n        throw new Error('Happy session ID is required: happy resume <session-id>');\n    }\n\n    const matches = records.filter((record) => record.id.startsWith(trimmed));\n    if (matches.length === 0) {\n        throw new Error(`No Happy session found matching \"${trimmed}\"`);\n    }\n    if (matches.length > 1) {\n        throw new Error(`Ambiguous Happy session \"${trimmed}\" matches ${matches.length} sessions. Be more specific.`);\n    }\n    return matches[0];\n}\n\nfunction decryptBoxBundle(bundle: Uint8Array, recipientSecretKey: Uint8Array): Uint8Array | null {\n    if (bundle.length < 56) {\n        return null;\n    }\n\n    const ephemeralPublicKey = bundle.slice(0, 32);\n    const nonce = bundle.slice(32, 56);\n    const ciphertext = bundle.slice(56);\n    const decrypted = tweetnacl.box.open(ciphertext, nonce, ephemeralPublicKey, recipientSecretKey);\n\n    return decrypted ? new Uint8Array(decrypted) : null;\n}\n\nfunction readAgentCredentials() {","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/resume/resolveHappySession.ts#L52-L88","documentation":"When the supplied prefix matches more than one session record, resolveSessionRecordByPrefix cannot choose and throws this ambiguity error listing the match count. Prefix-based resolution is only safe when it yields a single record, so the library forces the caller to supply a longer, more specific prefix.","triggerScenarios":"Calling resolveSessionRecordByPrefix(records, \"a1\") when records contains two or more session IDs beginning with \"a1\" — e.g. short prefixes colliding across sessions created around the same time.","commonSituations":"Using very short prefixes (1–2 characters) on accounts with many sessions; UUIDs sharing common prefixes (same first hex chars); shell history re-run with a partially remembered ID.","solutions":["Provide a longer prefix (or the full session ID) so exactly one session matches.","List sessions and pick the exact ID before resuming.","In tooling, add pre-validation: filter records by the prefix yourself and require matches.length === 1 before calling."],"exampleFix":"// before\nhappy resume a1            // matches 7 sessions\n// after\nhappy resume a1b2c3d4      // longer prefix, unique match","handlingStrategy":"validation","validationCode":"const matches = records.filter(r => r.id.startsWith(prefix));\nif (matches.length > 1) {\n  console.error(`Prefix \"${prefix}\" is ambiguous (${matches.length} matches). Use a longer prefix.`);\n  process.exit(1);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use at least 8+ characters of the session ID (or the full ID) as the prefix.","In tooling, resolve ambiguity interactively by listing matched sessions.","Avoid very short prefixes in scripts; require full IDs there."],"tags":["cli","session-lookup","ambiguity"],"backgroundTag":"ambiguous-identifier","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}