Forum Discussion

chuckwolber's avatar
chuckwolber
New Contributor
2 months ago

CLI Slow Performance

I have the 1Password desktop app installed and up to date on my macBook Pro, the `op` CLI is also installed, up to date, and working properly. All expected CLI queries work but they are surprisingly slow.

After a bunch of trial and error, it seems that it is making a round-trip online as part of every single CLI query. I added the --debug flag and I can see cache hits, but the round trip online is still occurring. Disabling the network interface causes all queries to fail.

Is it possible to get the 1Password CLI working fully offline to avoid all of this unnecessary round-trip business? Surely with the desktop app installed and CLI integration turned on, there has to be a way to make efficient (and offline) use of my 1Password vaults.

Otherwise automation tasks that require secrets are simply too cumbersome to handle with 1Password, and I will require a secondary solution. And in that case, I may as well give up on 1Password.

7 Replies

  • Will this ever get addressed?  I've seen posts as far back as 4 years.  1Password Team, please help!

    In case it's helpful to collect them, here are four from this forum, plus three more reports elsewhere on the internet:

    CLI commands are very slow | 1Password Community

    Why is the CLI so slow? Any tips? | 1Password Community 

    "op read" is pretty slow, ~700ms per invocation | 1Password Community 

    CLI cache is either not working or not significantly reducing time to return a secret | 1Password Community 

    https://www.reddit.com/r/1Password/comments/w9owk7/1password_cli_v_260_is_still_slow/ 

    https://github.com/jdx/mise/discussions/3542

    https://x.com/JukkaSuomela/status/1845065798231973953 

  • Could you share more details about the timing issues? How long are you waiting, and what setup are you using?

    In my case, the wait time is somewhere around 800ms to 1s. This is true for any read operation, eg `op item get`, `op read`, and `op inject` all take at least this long for a cached read. (Uncached can be even longer.) I'm running this from the command line on my M4 Max MacBook Pro.

    There are a number of things I have wanted to do with the 1password CLI, but this ~1s latency has been a dealbreaker for all of them.

    I didn't realize until this thread that the CLI talks to the 1Password API even for cached reads. This seems like a candidate for unnecessary latency?

    Finally, we recently introduced a the https://developer.1password.com/docs/sdks for more performant requirements. Take a peek, this may help.

    I just tried the Go and Python SDKs. Both took at least 800ms just to initialize the client, let alone read a secret.

  • Hi chuckwolber​ & supercoder​ ,

    Thanks for writing in and sharing your concern. 

    Could you share more details about the timing issues? How long are you waiting, and what setup are you using? It’ll help us identify any bottlenecks and better understand what you’re trying to achieve.

    A note about the CLI and Caches:

    The 1Password CLI caches items on Mac and Linux systems (not available on Windows). What it does not cache is the authentication to unlock 1Password.  You are seeing these calls.  After that you'll see the cache being accessed. (learn more here)

    Finally, we recently introduced a the 1Password SDK for more performant requirements. Take a peek, this may help.

    Thanks!
    Phil

    • chuckwolber's avatar
      chuckwolber
      New Contributor

      1P_Phil​

      macBook Pro 16" running macOS Sequoia 15.6.1, desktop client version: 8.11.8, 81108040 on PRODUCTION channel, and CLI version 2.32.0 installed via homebrew.

      Yes, I took a look at the SDK, it did not help. Every usage required at least a 1.2 second delay involving a round trip to the cloud. Pretty much a dealbreaker for a command line tool with a local data source (1P desktop client) available to it.

      Regarding the native 1P CLI...

      Without the `--cache` argument:

      ~$ time op items list > /dev/null real 0m4.302s user 0m0.122s sys 0m0.057s

      With the `--cache` argument:

      $ time op --cache items list > /dev/null real 0m0.986s user 0m0.119s sys 0m0.055s

      Something simple like listing vaults (which should be instant):

      $ time op vault list > /dev/null real 0m1.022s user 0m0.082s sys 0m0.037s $ time op --cache vault list > /dev/null real 0m1.103s user 0m0.083s sys 0m0.039s

      With all network disabled:

      $ time op --cache items list > /dev/null [ERROR] 2025/09/18 15:28:47 Get "https://my.1password.com/api/v2/account/keysets?__t=1758209327.444": dial tcp: lookup my.1password.com: no such host real 0m0.184s user 0m0.018s sys 0m0.025s

      As far as I can tell, a network round trip is required no matter what, which adds a full second for every CLI call.

      • jeremyschlatter's avatar
        jeremyschlatter
        New Contributor

        It's not just one network round trip. Using https://www.mitmproxy.org/ you can see that each CLI call makes multiple network round trips, in series. For a cached `op vault list`, it's two:

        HTTPS GET my.1password.com /api/v2/overview?__t=1758759373.782            200 …plication/json  3.1k 115ms
        HTTPS GET my.1password.com /api/v3/account?__t=1758759374.203&attrs=tier  200 …plication/json  2.0k 125ms

        For a cached `op read` it's four, all in series:

        HTTPS GET  my.1password.com /api/v2/overview?__t=1758759627.806            200 …plication/json  3.1k 118ms
        HTTPS GET  my.1password.com /api/v3/account?__t=1758759628.196&attrs=tier  200 …plication/json  2.0k 122ms
        HTTPS GET  my.1password.com /api/v3/account?__t=1758759628.354&attrs=tier  200 …plication/json  2.0k 110ms
        HTTPS GET  my.1password.com /api/v2/overview?__t=1758759628.490            200 …plication/json  3.1k 115ms

        The last two requests are exact duplicates of the first two, meaning the CLI could save two round trips per call to `op item read` just by keeping the results of `/api/v3/account` and `/api/v2/overview` in memory. It could save another round trip time by requesting both `/api/v3/account` and `/api/v2/overview` in parallel rather than in series.

        I also experimented with scripting mitmproxy to cache all of these requests. This effectively eliminates the network delay. The good news is that this saves ~220-500ms per CLI call (a little over 100ms per roundtrip). The bad news is that CLI calls still take a little over 400ms, which is still slow for reading at most a few kilobytes of data that is sitting locally on my machine. I'm not sure what the remaining slowdown is. Could be network roundtrips done by the 1password desktop app rather than the CLI, or could be something else.

        With network delay:

        $ hyperfine 'op vault list'
        Benchmark 1: op vault list
          Time (mean ± σ):     649.8 ms ±   9.1 ms    [User: 76.1 ms, System: 26.4 ms]
          Range (min … max):   636.9 ms … 664.8 ms    10 runs
        
        $ hyperfine 'op read "op://Private/gmail.com/username"'
        Benchmark 1: op read "op://Private/gmail.com/username"
          Time (mean ± σ):     929.9 ms ±  17.6 ms    [User: 159.6 ms, System: 49.6 ms]
          Range (min … max):   901.2 ms … 957.1 ms    10 runs

        Without network delay:

        $ HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080 hyperfine 'op vault list'
        Benchmark 1: op vault list
          Time (mean ± σ):     414.0 ms ±   6.9 ms    [User: 60.8 ms, System: 18.2 ms]
          Range (min … max):   397.4 ms … 423.2 ms    10 runs
        
        $ HTTP_PROXY=http://localhost:8080 HTTPS_PROXY=http://localhost:8080 hyperfine 'op read "op://Private/gmail.com/username"'
        Benchmark 1: op read "op://Private/gmail.com/username"
          Time (mean ± σ):     435.5 ms ±   7.6 ms    [User: 100.5 ms, System: 31.1 ms]
          Range (min … max):   422.1 ms … 449.8 ms    10 runs

        If you're interested in reproducing the mitmproxy caching, here is the script I used:

        from mitmproxy import http
        from urllib.parse import urlparse, parse_qs, urlencode
        import json
        import time
        
        class OnePasswordCache:
            def __init__(self):
                self.cache = {}
        
            def get_cache_key(self, flow):
                """Generate cache key by removing __t parameter"""
                url = urlparse(flow.request.pretty_url)
                params = parse_qs(url.query)
        
                # Remove __t parameter for cache key
                params.pop('__t', None)
        
                return (
                    url.scheme, url.netloc, url.path, urlencode(params),
                    flow.request.headers["x-agilebits-session-id"],
                )
        
            def request(self, flow: http.HTTPFlow):
                # Only cache GET requests to 1password API
                if (flow.request.method == "GET" and
                    "1password.com/api/" in flow.request.pretty_url):
        
                    cache_key = self.get_cache_key(flow)
        
                    if cache_key in self.cache:
                        # Serve from cache
                        cached_response = self.cache[cache_key]
        
                        # Create response from cache
                        flow.response = http.Response.make(
                            cached_response["status_code"],
                            cached_response["content"],
                            cached_response["headers"]
                        )
        
                        # Add header to indicate cache hit
                        flow.response.headers["X-Cache"] = "HIT"
        
            def response(self, flow: http.HTTPFlow):
                # Cache successful 1password API responses
                if (flow.request.method == "GET" and
                    "1password.com/api/" in flow.request.pretty_url and
                    flow.response.status_code == 200):
        
                    cache_key = self.get_cache_key(flow)
        
                    # Only cache if we didn't serve from cache
                    if "X-Cache" not in flow.response.headers:
                        self.cache[cache_key] = {
                            "status_code": flow.response.status_code,
                            "content": flow.response.content,
                            "headers": dict(flow.response.headers)
                        }
        
        addons = [OnePasswordCache()]

        Save as `cache_1password.py`, then run `mitmproxy -s cache_1password.py -p 8080`.

  • supercoder's avatar
    supercoder
    New Contributor

    Will this ever get addressed?  I've seen posts as far back as 4 years.  1Password Team, please help!