{"record":{"id":"87a729565c70a6ee","repo":"pentaho/pentaho-kettle","slug":"unable-to-invoke-repository-operation-after-reconnect","errorCode":null,"errorMessage":"Unable to invoke repository operation after reconnect","messagePattern":"Unable to invoke repository operation after reconnect","errorType":"exception","errorClass":"SessionRecoveryRetryException","httpStatus":null,"severity":"error","filePath":"plugins/repositories/core/src/main/java/org/pentaho/di/ui/repo/timeout/RepositorySessionTimeoutHandler.java","lineNumber":151,"sourceCode":"  private Object reconnectAndRetry( Method method, Object[] args )\n      throws SessionRecoveryRetryException {\n    try {\n      ConnectionManager.getInstance().reset();\n    } catch ( Exception ce ) {\n      // Intentionally ignore cache reset errors - they should not prevent reconnection\n    }\n    try {\n      metaStoreInstance = wrapMetastoreWithTimeoutHandler( MetaStoreConst.getDefaultMetastore(), sessionTimeoutHandler );\n    } catch ( Exception me ) {\n      // Intentionally ignore metastore refresh errors - they should not prevent reconnection\n    }\n    try {\n      return method.invoke( this.repository, args );\n    } catch ( InvocationTargetException retryEx ) {\n      throw new SessionRecoveryRetryException( \"Failed to retry repository operation after reconnect\",\n          retryEx.getCause() );\n    } catch ( IllegalAccessException | IllegalArgumentException retryEx ) {\n      throw new SessionRecoveryRetryException( \"Unable to invoke repository operation after reconnect\", retryEx );\n    }\n  }\n\n  static class SessionRecoveryRetryException extends KettleException {\n    private static final long serialVersionUID = 1L;\n\n    SessionRecoveryRetryException( String message, Throwable cause ) {\n      super( message, cause );\n    }\n  }\n\n  boolean connectedToRepository() {\n    return repository.isConnected();\n  }\n\n  IRepositoryService wrapRepositoryServiceWithTimeoutHandler( Class<? extends IRepositoryService> clazz )\n    throws KettleException {\n    IRepositoryService service = repository.getService( clazz );","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/repositories/core/src/main/java/org/pentaho/di/ui/repo/timeout/RepositorySessionTimeoutHandler.java#L133-L169","documentation":"RepositorySessionTimeoutHandler.reconnectAndRetry throws SessionRecoveryRetryException('Unable to invoke repository operation after reconnect') when the reflective re-invocation fails with IllegalAccessException or IllegalArgumentException — i.e. reflection could not legally call the method (inaccessible method, mismatched argument types/receiver), rather than the method itself failing. This indicates a programming or proxy mismatch problem, not a server problem.","triggerScenarios":"The repository proxy passes arguments that no longer match the method signature, the method is not accessible from the handler's package/class loader, or the receiver instance is of the wrong type after reconnection.","commonSituations":"Classloader issues with plugins after reconnect; custom Repository implementations whose methods are not public; version upgrade changed a method signature the handler caches.","solutions":["Ensure the intercepted repository methods are public and the argument types exactly match the Method object's declaring class","Re-obtain the Method object from the current instance's class instead of caching it across reconnects","Check for plugin classloader conflicts after reconnection"],"exampleFix":"// before\nMethod m = Repository.class.getMethod( \"save\", ... ); // cached, may mismatch instance\n// after\nMethod m = repository.getClass().getMethod( \"save\", ... ); // resolve from live instance","handlingStrategy":"validation","validationCode":"// verify the method is reflectively invokable before reconnect flows\nboolean isInvokable( Object receiver, Method m, Object[] args ) {\n  if ( !m.getDeclaringClass().isInstance( receiver ) ) return false;\n  Class<?>[] pt = m.getParameterTypes();\n  if ( args.length != pt.length ) return false;\n  for ( int i = 0; i < pt.length; i++ ) {\n    if ( args[i] != null && !pt[i].isInstance( args[i] ) ) return false;\n  }\n  return Modifier.isPublic( m.getModifiers() );\n}","typeGuard":null,"tryCatchPattern":"try {\n  handler.handle( proxy, method, args );\n} catch ( SessionRecoveryRetryException e ) {\n  if ( e.getCause() instanceof IllegalAccessException\n       || e.getCause() instanceof IllegalArgumentException ) {\n    log.error( \"Reflection mismatch in timeout handler\", e.getCause() ); // bug, not transient\n  } else { throw e; }\n}","preventionTips":["Keep intercepted repository methods public and signatures stable across upgrades","Resolve Method objects from the live instance's class each time, never cache across reconnects","Beware plugin classloader boundaries when re-invoking after reconnection"],"tags":["reflection","session","repository"],"backgroundTag":"method-invocation-failed","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}