{"record":{"id":"4faf83db7ff24c7b","repo":"basecamp/kamal","slug":"could-not-find-missing-items-join-in-last","errorCode":null,"errorMessage":"Could not find #{missing_items.join(\", \")} in LastPass","messagePattern":"Could not find #(.+?) in LastPass","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/last_pass.rb","lineNumber":27,"sourceCode":"\n    def loggedin?(account)\n      `lpass status --color never`.strip == \"Logged in as #{account}.\"\n    end\n\n    def fetch_secrets(secrets, from:, account:, session:)\n      secrets = prefixed_secrets(secrets, from: from)\n      items = `lpass show #{secrets.map(&:shellescape).join(\" \")} --json`\n      raise RuntimeError, \"Could not read #{secrets} from LastPass\" unless $?.success?\n\n      items = JSON.parse(items)\n\n      {}.tap do |results|\n        items.each do |item|\n          results[item[\"fullname\"]] = item[\"password\"]\n        end\n\n        if (missing_items = secrets - results.keys).any?\n          raise RuntimeError, \"Could not find #{missing_items.join(\", \")} in LastPass\"\n        end\n      end\n    end\n\n    def check_dependencies!\n      raise RuntimeError, \"LastPass CLI is not installed\" unless cli_installed?\n    end\n\n    def cli_installed?\n      `lpass --version 2> /dev/null`\n      $?.success?\n    end\nend\n","sourceCodeStart":9,"sourceCodeEnd":41,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/last_pass.rb#L9-L41","documentation":"Raised in LastPass#fetch_secrets after `lpass show ... --json` SUCCEEDED: the JSON is parsed into a {fullname => password} map, then `secrets - results.keys` is computed. If any requested name was not returned under an identical key (the item's 'fullname'), the missing list is joined into this RuntimeError. It is a post-fetch completeness check — the failure mode is name mismatch between your secrets list and the returned fullnames, not a CLI failure.","triggerScenarios":"adapter.fetch([...]) where lpass returns items whose 'fullname' differs from the requested string: entry stored in a group/shared folder has fullname 'GroupName/EntryName' while you requested 'EntryName'; item type mismatch (site URL vs name); duplicates where lpass returns only one match; or requesting a name that exists but whose returned fullname includes a path prefix.","commonSituations":"Shared-folder or grouped entries referenced by short name; renaming a folder in LastPass so fullnames shift; mixing ID-based and name-based references across team members.","solutions":["List the exact fullname: `lpass ls --format \"%an\"` (or plain `lpass ls`) and copy the path-qualified name into your kamal secrets list (e.g. 'Shared-Prod/RAILS_MASTER_KEY').","If you prefer short names, move the entry to the vault root (no group) in the LastPass UI so its fullname equals the name.","Use the numeric ID as the requested key — IDs are stable across renames and folder moves."],"exampleFix":"# before\nsecrets: [ \"DB_PASSWORD\" ]           # lives in group 'Prod'\n# -> RuntimeError: Could not find DB_PASSWORD in LastPass\n\n# after: use the fullname as printed by `lpass ls`\nsecrets: [ \"Prod/DB_PASSWORD\" ]","handlingStrategy":"validation","validationCode":"require \"open3\"\nrequire \"json\"\n\ndef lpass_fullnames(names)\n  names.map do |n|\n    out, _err, st = Open3.capture3(\"lpass\", \"show\", n, \"--json\")\n    next nil unless st.success?\n    items = JSON.parse(out)\n    items.flat_map { |i| i[\"fullname\"] }\n  end.compact.flatten\nend\n\nmissing = names - lpass_fullnames(names)\nabort \"LastPass fullname mismatch for: #{missing.join(', ')} — use paths from `lpass ls`\" if missing.any?","typeGuard":null,"tryCatchPattern":"begin\n  secrets = adapter.fetch(names, account: ACCOUNT)\nrescue RuntimeError => e\n  if (m = e.message.match(/Could not find (.+) in LastPass/))\n    raise \"#{m[1]} not returned by lpass: request the path-qualified fullname (see `lpass ls`) or move the entry to vault root\"\n  end\n  raise\nend","preventionTips":["Reference grouped/shared-folder entries by their full path as printed by `lpass ls` (GroupName/EntryName).","Use numeric entry IDs in deploy.yml for stability across folder renames.","After reorganizing the LastPass vault, re-run `lpass ls` and update the secrets list before deploying."],"tags":["ruby","kamal","lastpass","secrets","secret-not-found","name-mismatch"],"backgroundTag":"secret-not-found","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}