Skip to content

[Bug] Timeout not working for Local LLM with MLX Models #2974

Closed

Description

@webboty

There is still an issue that with a local provider like "lm studio" serving MLX models, timeout settings in the opencode.json config are totally ignored.

This break a lot of workflows especially with subagents.
While the main chat window might be continued via "continue", subagents once timeout can not be continued.

Running bigger local models especially with bigger CTX may sometimes require more than the default timeout setting.

Here is a fraction of a config that shows how the timeout is set and yet ognored.

[...]
 "provider": {
    "lmstudio": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "LM Studio (local)",
      "options": {
        "baseURL": "http://192.168.1.107:11435/api/v0",
        "timeout": 900000,
        "includeUsage": true
      },
      "models": {
[...]

Activity

  1. github-actions commented on Oct 4, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  2. rekram1-node commented on Oct 5, 2025

    @rekram1-node
    Contributor

    just wanted to comment that I see this and I will give you a work around or fix when I get a chance

  3. kaantekiner commented on Oct 12, 2025

    @kaantekiner

    hi, didnt #1982 solved this, thanks.

  4. rekram1-node commented on Oct 12, 2025

    @rekram1-node
    Contributor

    no doesnt solve this one

  5. kaantekiner commented on Oct 13, 2025

    @kaantekiner

    @rekram1-node , thanks for very quick response. I tried with local ollama, like the #1065 but it also failed to progress the 200 ms timeout threshod. So it is not working for all local providers, not just for lmstudio, can you please confirm?

    { "$schema": "https://opencode.ai/config.json", "provider": { "ollama": { "npm": "@ai-sdk/openai-compatible", "name": "local ollama server", "options": { "baseURL": "http://xxxx:11434/v1", "timeout": 200 },

    Thanks in advance.

  6. rekram1-node commented on Oct 13, 2025

    @rekram1-node
    Contributor

    @kaantekiner not sure i entirely follow, if you are setting the timeout to a smaller value it will work but if it is larger than 5 minutes in some cases it won't work because bun has underlying timeout of 5 minutes if no data is being sent from the server

  7. kaantekiner commented on Oct 13, 2025

    @kaantekiner

    @rekram1-node I tried the timeout with 200 MS (like the above snippet) and few seconds, but no luck, it did not respect at all, no timeout on cli.

    Also passed 'option' parameters like 'num_ctx' to ollama not worked as well. I didn't set a reverse proxy for api observation (opencode --> ollama api), but 'ollama serve' logs did not aligned with num_ctx while booting the model. But somehow when options have 'reasoning': true, and if model does not have that feature, it tirggers the error at cli, which is correct.

    Need some deeper investigation I believe.

  8. rekram1-node commented on Oct 13, 2025

    @rekram1-node
    Contributor

    ahh i see, wait for num_ctx you need to set that in ollama that's not an option to pass

  9. kaantekiner commented on Oct 13, 2025

    @kaantekiner

    ahh i see, wait for num_ctx you need to set that in ollama that's not an option to pass

    exactly, this approach below remediates. I have seen it from some other dev, it is working.

    for the timeout thing, I can wait.

  10. rekram1-node commented on Oct 13, 2025

    @rekram1-node
    Contributor

    Sounds good, hm weird that timeout isn't working at all I thought for short ones it'd be enforced lol well thanks for finding this!

  11. webboty commented on Oct 14, 2025

    @webboty
    Author

    @kaantekiner not sure i entirely follow, if you are setting the timeout to a smaller value it will work but if it is larger than 5 minutes in some cases it won't work because bun has underlying timeout of 5 minutes if no data is being sent from the server

    Do you have any idea how we could get around that?

    Like an intermediate service (like a bridge listener) the request are funneled through and responds with something before the 5 minutes are over? Or a custom version of bun where this stupid hard coded 5 minute threshold is eliminated? 5 minutes in LLM world is not a lot. :-(

  12. rekram1-node commented on Oct 14, 2025

    @rekram1-node
    Contributor

    yeah ill take a look at that tmr, but just so we are clear the timeout should only be occurring if nothing is being sent from server, if the llm is actively doign things the request could last long long periods of time

    But totally yeah we should be able to work around that default

  13. webboty commented on Oct 14, 2025

    @webboty
    Author

    yeah ill take a look at that tmr, but just so we are clear the timeout should only be occurring if nothing is being sent from server, if the llm is actively doign things the request could last long long periods of time

    But totally yeah we should be able to work around that default

    You are right, once the llm starts streaming results it is, all working. With bigger context and bigger model (despite great hardware) it can take more than 5 minutes until the llm starts streaming.

    so the time to first token is the big issue.

  14. rekram1-node commented on Oct 16, 2025

    @rekram1-node
    Contributor

    found the issue, fixing now

  15. rekram1-node commented on Oct 16, 2025

    @rekram1-node
    Contributor

    will be fixed in next release

  16. rawzone commented on Oct 24, 2025

    @rawzone

    will be fixed in next release

    Sadly didn't make it in the last release?

    Any timeline for the to merge in release? As of now it stops at least me from using opencode as every prompt fails with a timeout. (Yes I run the LLM om a bit of older hardware. But Im sure many of us are).

  17. rekram1-node commented on Oct 24, 2025

    @rekram1-node
    Contributor

    @rawzone it was released, are you sure the setting doesn't work for you I tested it with proxy servers and it was working for me. What version of opencode are you using?

    This is what setting looks like:

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "my-provider": {
          "options": {
            "timeout": 100000000000
          }
        }
      }
    }
    

    Timeout is in milliseconds

  18. rawzone commented on Oct 24, 2025

    @rawzone

    Thanks @rekram1-node !

    It does in fact seem to work as intended now.
    Was just going by the text on the "releases" and didn't say anything about the timeout issue.

    Now I can run opencode to help me out with projects (slowly, but without timeouts on this old dual Xeon CPU server), yay!

    Have a great weekend. 🫡

  19. rekram1-node commented on Oct 24, 2025

    @rekram1-node
    Contributor

    Sweet, you too

  20. aagarin39 commented on Mar 18, 2026

    @aagarin39

    Fix: Increase chunk timeout & auto-disable for local URLs

    PROBLEM: When LLM responses take a long time (especially reasoning models like o1, Claude thinking, DeepSeek), opencode prematurely terminates the connection after 5 minutes due to DEFAULT_CHUNK_TIMEOUT = 300_000.

    SOLUTION:

    Increase default timeout from 5 to 15 minutes
    Auto-detect local URLs and disable timeout
    Allow manual override
    CODE CHANGES:

    File: packages/opencode/src/provider/provider.ts

    // Line 50-52: Increased default from 300_000 to 900_000
    // 15 minutes default chunk timeout for streaming responses
    // Increased from 5 minutes to support long-running reasoning models (o1, Claude thinking, DeepSeek, etc.)
    const DEFAULT_CHUNK_TIMEOUT = 900_000

    // Line 54-70: New function to detect local URLs
    export function isLocalURL(url: string | undefined): boolean {
    if (!url) return false
    try {
    const parsed = new URL(url)
    const hostname = parsed.hostname.toLowerCase()
    // localhost and loopback
    if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true
    // Private IPv4 ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
    if (/^(10.|172.(1[6-9]|2[0-9]|3[01]).|192.168.)/.test(hostname)) return true
    // Private IPv6 ranges (fc00::/7)
    if (/^fc00:/i.test(hostname) || /^fd[0-9a-f]{2}:/i.test(hostname)) return true
    return false
    } catch {
    return false
    }
    }

    // Line 1154-1164: Auto-detect logic
    const customFetch = options["fetch"]
    let chunkTimeout: number
    if (options["chunkTimeout"] !== undefined && options["chunkTimeout"] !== null) {
    chunkTimeout = options["chunkTimeout"] // User override
    } else {
    // Auto-detect: disable timeout for local URLs, use default for remote
    chunkTimeout = isLocalURL(baseURL) ? 0 : DEFAULT_CHUNK_TIMEOUT
    }
    delete options["chunkTimeout"]

    // Line 1170: Only create AbortController if timeout > 0
    const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
    File: packages/opencode/src/config/config.ts

    // Line 1021-1029: Updated schema to allow 0
    chunkTimeout: z
    .union([
    z.number().int().min(0).describe("Timeout in milliseconds (0 to disable)."),
    z.literal(0).describe("Disable chunk timeout entirely."),
    ])
    .optional()
    .describe(
    "Timeout in milliseconds between streamed SSE chunks. Default: 900000 (15 min) for remote URLs, 0 (disabled) for local URLs (localhost, 127.x.x.x, 192.168.x.x, 10.x.x.x). Set to 0 to disable timeout.",
    ),
    File: packages/opencode/test/provider/provider.test.ts

    // Line 10-90: Unit tests for isLocalURL()
    test("isLocalURL detects localhost", () => {
    expect(Provider.isLocalURL("http://localhost:11434")).toBe(true)
    expect(Provider.isLocalURL("http://localhost:8080/v1")).toBe(true)
    })

    test("isLocalURL detects 127.0.0.1", () => {
    expect(Provider.isLocalURL("http://127.0.0.1:8080")).toBe(true)
    })

    test("isLocalURL detects private IPv4 ranges", () => {
    expect(Provider.isLocalURL("http://10.0.0.1:8080")).toBe(true)
    expect(Provider.isLocalURL("http://172.16.0.1:8080")).toBe(true)
    expect(Provider.isLocalURL("http://192.168.0.1:8080")).toBe(true)
    })

    test("isLocalURL returns false for public URLs", () => {
    expect(Provider.isLocalURL("https://api.openai.com")).toBe(false)
    expect(Provider.isLocalURL("http://8.8.8.8:8080")).toBe(false)
    })

    // Line 18-178: Integration tests
    test("chunkTimeout auto-disabled for localhost URLs", async () => {
    // Config with localhost URL
    const config = {
    provider: {
    ollama: {
    id: "llama3",
    npm: "@ai-sdk/openai-compatible",
    baseURL: "http://localhost:11434/v1",
    },
    },
    }
    // Verify chunkTimeout is 0
    expect(providers["ollama"].options.chunkTimeout).toBe(0)
    })

    test("chunkTimeout uses default for public URLs", async () => {
    // Remote URL should get 900000ms (15 min)
    expect(providers["remote"].options.chunkTimeout).toBe(900000)
    })
    CONFIGURATION EXAMPLES:

    Local server (automatic - no timeout):
    {
    "provider": {
    "ollama": {
    "baseURL": "http://localhost:11434/v1"
    }
    }
    }
    Manual override:
    {
    "provider": {
    "slow-provider": {
    "options": {
    "chunkTimeout": 0
    }
    }
    }
    }
    Custom timeout:
    {
    "provider": {
    "provider": {
    "options": {
    "chunkTimeout": 1800000
    }
    }
    }
    }
    BACKWARD COMPATIBILITY:

    Existing configs continue working
    Default increased from 5 to 15 minutes
    No breaking changes
    REQUEST FOR REVIEW:

    We'd appreciate your review of this solution:

    Minimal: Only 3 files modified, ~100 lines of code
    Safe: Fully backward compatible
    Tested: 12 new tests
    Questions:

    Is 15 minutes appropriate or should we consider 30 minutes?
    Should isLocalURL() be configurable for advanced users?
    Any concerns about the private IP detection logic?
    Looking forward to your feedback!

  21. aagarin39 commented on Mar 19, 2026

    @aagarin39

    Fix: Increase chunk timeout & auto-disable for local URLs

    PROBLEM:
    When LLM responses take a long time (especially reasoning models like o1, Claude thinking, DeepSeek), opencode prematurely terminates the connection after 5 minutes due to DEFAULT_CHUNK_TIMEOUT = 300_000.

    SOLUTION:

    1. Increase default timeout from 5 to 15 minutes
    2. Auto-detect local URLs and disable timeout
    3. Allow manual override

    CODE CHANGES:

    File: packages/opencode/src/provider/provider.ts

    // Line 50-52: Increased default from 300_000 to 900_000
    // 15 minutes default chunk timeout for streaming responses
    // Increased from 5 minutes to support long-running reasoning models (o1, Claude thinking, DeepSeek, etc.)
    const DEFAULT_CHUNK_TIMEOUT = 900_000
    
    // Line 54-70: New function to detect local URLs
    export function isLocalURL(url: string | undefined): boolean {
      if (!url) return false
      try {
        const parsed = new URL(url)
        const hostname = parsed.hostname.toLowerCase()
        // localhost and loopback
        if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true
        // Private IPv4 ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
        if (/^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/.test(hostname)) return true
        // Private IPv6 ranges (fc00::/7)
        if (/^fc00:/i.test(hostname) || /^fd[0-9a-f]{2}:/i.test(hostname)) return true
        return false
      } catch {
        return false
      }
    }
    
    // Line 1154-1164: Auto-detect logic
    const customFetch = options["fetch"]
    let chunkTimeout: number
    if (options["chunkTimeout"] !== undefined && options["chunkTimeout"] !== null) {
      chunkTimeout = options["chunkTimeout"]  // User override
    } else {
      // Auto-detect: disable timeout for local URLs, use default for remote
      chunkTimeout = isLocalURL(baseURL) ? 0 : DEFAULT_CHUNK_TIMEOUT
    }
    delete options["chunkTimeout"]
    
    // Line 1170: Only create AbortController if timeout > 0
    const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined

    File: packages/opencode/src/config/config.ts

    // Line 1021-1029: Updated schema to allow 0
    chunkTimeout: z
      .union([
        z.number().int().min(0).describe("Timeout in milliseconds (0 to disable)."),
        z.literal(0).describe("Disable chunk timeout entirely."),
      ])
      .optional()
      .describe(
        "Timeout in milliseconds between streamed SSE chunks. Default: 900000 (15 min) for remote URLs, 0 (disabled) for local URLs (localhost, 127.x.x.x, 192.168.x.x, 10.x.x.x). Set to 0 to disable timeout.",
      ),

    File: packages/opencode/test/provider/provider.test.ts

    // Line 10-90: Unit tests for isLocalURL()
    test("isLocalURL detects localhost", () => {
      expect(Provider.isLocalURL("http://localhost:11434")).toBe(true)
      expect(Provider.isLocalURL("http://localhost:8080/v1")).toBe(true)
    })
    
    test("isLocalURL detects 127.0.0.1", () => {
      expect(Provider.isLocalURL("http://127.0.0.1:8080")).toBe(true)
    })
    
    test("isLocalURL detects private IPv4 ranges", () => {
      expect(Provider.isLocalURL("http://10.0.0.1:8080")).toBe(true)
      expect(Provider.isLocalURL("http://172.16.0.1:8080")).toBe(true)
      expect(Provider.isLocalURL("http://192.168.0.1:8080")).toBe(true)
    })
    
    test("isLocalURL returns false for public URLs", () => {
      expect(Provider.isLocalURL("https://api.openai.com")).toBe(false)
      expect(Provider.isLocalURL("http://8.8.8.8:8080")).toBe(false)
    })
    
    // Line 18-178: Integration tests
    test("chunkTimeout auto-disabled for localhost URLs", async () => {
      // Config with localhost URL
      const config = {
        provider: {
          ollama: {
            id: "llama3",
            npm: "@ai-sdk/openai-compatible",
            baseURL: "http://localhost:11434/v1",
          },
        },
      }
      // Verify chunkTimeout is 0
      expect(providers["ollama"].options.chunkTimeout).toBe(0)
    })
    
    test("chunkTimeout uses default for public URLs", async () => {
      // Remote URL should get 900000ms (15 min)
      expect(providers["remote"].options.chunkTimeout).toBe(900000)
    })

    CONFIGURATION EXAMPLES:

    1. Local server (automatic - no timeout):
    {
      "provider": {
        "ollama": {
          "baseURL": "http://localhost:11434/v1"
        }
      }
    }
    1. Manual override:
    {
      "provider": {
        "slow-provider": {
          "options": {
            "chunkTimeout": 0
          }
        }
      }
    }
    1. Custom timeout:
    {
      "provider": {
        "provider": {
          "options": {
            "chunkTimeout": 1800000
          }
        }
      }
    }

    BACKWARD COMPATIBILITY:

    • Existing configs continue working
    • Default increased from 5 to 15 minutes
    • No breaking changes

    REQUEST FOR REVIEW:

    We'd appreciate your review of this solution:

    • Minimal: Only 3 files modified, ~100 lines of code
    • Safe: Fully backward compatible
    • Tested: 12 new tests

    Questions:

    1. Is 15 minutes appropriate or should we consider 30 minutes?
    2. Should isLocalURL() be configurable for advanced users?
    3. Any concerns about the private IP detection logic?

    Looking forward to your feedback!

    This is a simpler and more obvious solution than the previous one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions