{"record":{"id":"d3763bb6aca4af6f","repo":"linera-io/linera-protocol","slug":"keypair-not-found-for-chain-chain-id","errorCode":null,"errorMessage":"keypair not found for chain `{chain_id}`","messagePattern":"keypair not found for chain `(.+?)`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-wallet-json/src/wallet.rs","lineNumber":200,"sourceCode":"\n    /// Applies a mutation to the chain with the given ID, saving afterwards. Returns `None`\n    /// if the chain is not in the wallet.\n    pub fn mutate<R>(\n        &self,\n        chain_id: ChainId,\n        mutate: impl Fn(&mut Chain) -> R,\n    ) -> Option<Result<R, persistent::file::Error>> {\n        self.0\n            .chains\n            .mutate(chain_id, mutate)\n            .map(|outcome| self.0.save().map(|()| outcome))\n    }\n\n    /// Removes and returns the owner of the given chain, erroring if the chain or owner is absent.\n    pub fn forget_keys(&self, chain_id: ChainId) -> anyhow::Result<AccountOwner> {\n        self.mutate(chain_id, |chain| chain.owner.take())\n            .ok_or_else(|| anyhow::anyhow!(\"nonexistent chain `{chain_id}`\"))??\n            .ok_or_else(|| anyhow::anyhow!(\"keypair not found for chain `{chain_id}`\"))\n    }\n\n    /// Writes the wallet to its file.\n    pub fn save(&self) -> Result<(), persistent::file::Error> {\n        self.0.save()\n    }\n\n    /// Returns the number of chains in the wallet.\n    pub fn num_chains(&self) -> usize {\n        self.0.chains.items().len()\n    }\n\n    /// Returns the IDs of all chains in the wallet.\n    pub fn chain_ids(&self) -> Vec<ChainId> {\n        self.0.chains.chain_ids()\n    }\n\n    /// Returns the list of all chain IDs for which we have a secret key.","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-wallet-json/src/wallet.rs#L182-L218","documentation":"In forget_keys, mutate found the chain entry but the closure chain.owner.take() returned None — the chain exists in the wallet yet has no owner/keypair attached (already forgotten, or a chain tracked watch-only). This inner Option is converted into the 'keypair not found' anyhow error.","triggerScenarios":"Calling forget_keys twice on the same chain; forgetting keys of a chain the wallet tracks without owning (e.g. a read-only/multi-owner chain where this wallet never held the keypair).","commonSituations":"Idempotency-unaware scripts that retry a forget operation after a timeout; wallet state changed by another process between listing and forgetting; user assuming 'forget' resets the chain rather than removing key material.","solutions":["Treat it as already-done if your workflow is idempotent: match the error and continue when the keys were previously forgotten","Inspect wallet chain state first: check whether owned_chain_ids() contains the chain before calling forget_keys","Do not retry blindly — the wallet file was already saved without the owner on the first successful call"],"exampleFix":"// before\nlet owner = wallet.forget_keys(chain_id)?; // second call -> error\n\n// after (idempotent helper)\nlet owner = match wallet.forget_keys(chain_id) {\n    Ok(owner) => owner,\n    Err(e) if e.to_string().contains(\"keypair not found\") => {\n        return Ok(None); // keys were already forgotten\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"// Only attempt to forget keys we actually own\nif wallet.owned_chain_ids().contains(&chain_id) {\n    wallet.forget_keys(chain_id)?;\n} else {\n    tracing::warn!(%chain_id, \"no keys held for chain; skipping\");\n}","typeGuard":null,"tryCatchPattern":"let owner = match wallet.forget_keys(chain_id) {\n    Ok(owner) => Some(owner),\n    Err(e) if e.to_string().contains(\"keypair not found\") => None, // already forgotten\n    Err(e) => return Err(e),\n};","preventionTips":["Make forget workflows idempotent: match the 'keypair not found' message and treat as success","Do not retry the operation on error — the wallet may already be saved without the owner","Guard multi-step scripts with owned_chain_ids() membership checks"],"tags":["linera","wallet","keypair","not-found","rust"],"backgroundTag":"keypair-not-found","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}