{"record":{"id":"d275d40c1c3fdf9b","repo":"FiloSottile/age","slug":"received-duplicated-file-key-stanza","errorCode":null,"errorMessage":"received duplicated file-key stanza","messagePattern":"received duplicated file-key stanza","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/client.go","lineNumber":281,"sourceCode":"\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch s.Type {\n\t\tcase \"file-key\":\n\t\t\tif len(s.Args) != 1 {\n\t\t\t\treturn nil, fmt.Errorf(\"malformed file-key stanza: unexpected arguments count\")\n\t\t\t}\n\t\t\tn, err := strconv.Atoi(s.Args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"malformed file-key stanza: invalid index\")\n\t\t\t}\n\t\t\t// We only send a single file key, so the index must be 0.\n\t\t\tif n != 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"malformed file-key stanza: unexpected index\")\n\t\t\t}\n\t\t\tif fileKey != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"received duplicated file-key stanza\")\n\t\t\t}\n\n\t\t\tfileKey = s.Body\n\n\t\t\tif err := writeStanza(conn, \"ok\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"error\":\n\t\t\tif err := writeStanza(conn, \"ok\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn nil, fmt.Errorf(\"%s\", s.Body)\n\t\tcase \"done\":\n\t\t\tbreak ReadLoop\n\t\tdefault:\n\t\t\tif ok, err := i.ui.handle(i.name, conn, s); err != nil {\n\t\t\t\treturn nil, err","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/plugin/client.go#L263-L299","documentation":"The plugin sent a second file-key stanza within one unwrap session. The client already received and acknowledged one file key; the protocol allows only one per invocation, so a duplicate indicates a buggy plugin that is answering the same request twice.","triggerScenarios":"During Unwrap, the plugin writes \"file-key\" again after the client has already stored fileKey and replied with an \"ok\" stanza.","commonSituations":"Plugin loops that don't break after answering the file-key request; plugin handling both wrap and unwrap paths incorrectly; a plugin that retries internally after a slow \"ok\" acknowledgment.","solutions":["Update the plugin to send the file-key stanza exactly once per session, then wait for \"done\"","Audit the plugin's state machine: after replying to file-key it should only send \"error\" or \"done\"","Test the plugin against age-plugin-test-tool or the official age client to catch duplicate stanzas"],"exampleFix":"// before (plugin side)\nfor { handleStanza(); if isFileKey { sendFileKey() } } // loops forever\n// after\nhandleFileKeyRequest(); sendFileKey(); awaitDone()","handlingStrategy":"validation","validationCode":"// In plugin code, assert single-send before writing file-key\nif sent { t.Fatal(\"file-key sent twice\") }; sent = true","typeGuard":null,"tryCatchPattern":"err := identity.Unwrap(...)\nif err != nil && strings.Contains(err.Error(), \"duplicated file-key\") {\n    return fmt.Errorf(\"plugin %s sent file-key twice; plugin state machine bug\", pluginName)\n}","preventionTips":["Structure plugin unwrap as: read stanza -> answer once -> await done","Add integration tests asserting one file-key per session","Use the age-plugin-test-tool to catch protocol violations"],"tags":["go","age","plugin-protocol","duplicate-stanza"],"backgroundTag":"plugin-protocol-violation","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}