ollama - 💡(How to fix) Fix cmd: PushHandler panics after a successful push

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x...]

goroutine 1 [running]: github.com/ollama/ollama/progress.(*Spinner).Stop(...) progress/spinner.go:75 github.com/ollama/ollama/cmd.PushHandler(...) cmd/cmd.go:995

Fix Action

Fix

 p.Stop()
-spinner.Stop()
+if spinner != nil {
+    spinner.Stop()
+}

Same pattern the error path already uses. Happy to open a PR.

Code Example

var spinner *progress.Spinner  // nil

---

} else if status != resp.Status {
    ...
    spinner = progress.NewSpinner(status)  // <-- only assignment
}

---

if err := client.Push(cmd.Context(), &request, fn); err != nil {
    if spinner != nil { spinner.Stop() }  // <-- guarded
    ...
}

---

p.Stop()
spinner.Stop()  // <-- no nil check, will panic if nil

---

flowchart TD
    A["PushHandler() called"] --> B["var spinner *progress.Spinner<br/>= nil"]
    B --> C["client.Push() with fn callback"]
    C --> D{"ctx err or<br/>Push err?"}

    D -->|"error"| E["error path<br/>if spinner != nil { spinner.Stop() } ✅"]
    E --> Z["return err"]

    D -->|"no error (success)"| F["p.Stop()"]
    F --> G["spinner.Stop()"]
    G --> H{"spinner == nil?"}

    H -->|"YES"| I["💥 PANIC<br/>nil pointer dereference"]
    H -->|"NO"| J["stop spinner normally ✅"]

    style I fill:#ff4444,stroke:#000,color:#fff
    style E fill:#44aa44,stroke:#000,color:#fff
    style J fill:#44aa44,stroke:#000,color:#fff

---

flowchart TD
    CB["fn callback receives ProgressResponse"] --> Q1{"resp.Digest != ''?"}

    Q1 -->|"YES (blob response)"| B1["if spinner != nil { spinner.Stop() }"]
    B1 --> B2["create/update bar for digest"]
    B2 --> RET["return nil"]

    Q1 -->|"NO (status response)"| Q2{"status != resp.Status?"}
    Q2 -->|"NO"| RET
    Q2 -->|"YES"| S1["if spinner != nil { spinner.Stop() }"]
    S1 --> S2["status = resp.Status"]
    S2 --> S3["spinner = progress.NewSpinner(status)"]
    S3 --> S4["p.Add(status, spinner)"]
    S4 --> RET

    S3 -.->|"🟢 only place spinner is assigned"| S3

---

sequenceDiagram
    actor CLI as ollama push
    participant Server as Registry/Server
    participant Handler as PushHandler

    rect rgb(200, 255, 200)
        Note over CLI,Handler: Normal Production Flow
        CLI->>Server: POST /api/push
        Server-->>Handler: {Status: "retrieving manifest"}<br/>Digest = ""
        Note over Handler: status != "" → spinner created ✅
        Server-->>Handler: {Digest: "sha256:abc", ...}
        Note over Handler: Digest != "" → bar updated
        Server-->>Handler: {Status: "pushing manifest"}<br/>Digest = ""
        Note over Handler: status changed → spinner updated
        Server-->>Handler: {Status: "success"}
        Note over Handler: spinner.Stop() → safe ✅
    end

    rect rgb(255, 200, 200)
        Note over CLI,Handler: Edge case (no status-only responses)
        CLI->>Server: POST /api/push
        Server-->>Handler: {Digest: "sha256:abc", Total:100, Completed:50}<br/>Digest != ""
        Note over Handler: bar created, spinner still nil
        Server-->>Handler: {Digest: "sha256:abc", Total:100, Completed:100}<br/>Digest != ""
        Note over Handler: bar updated, spinner still nil
        Server-->>Handler: {Digest: "sha256:xyz", Total:100, Completed:100}<br/>Digest != ""
        Note over Handler: bar updated, spinner STILL nil
        Note over Handler: 💥 spinner.Stop() on nil → PANIC
    end

---

flowchart LR
    subgraph PushHandler
        direction TB
        PH_E["Error path"] -->|"guarded ✅"| PH_EG["if spinner != nil {<br/>  spinner.Stop()<br/>}"]
        PH_S["Success path"] -->|"unguarded ❌"| PH_SG["spinner.Stop()"]
    end

    subgraph PullHandler
        direction TB
        PL_S["Success path"] -->|"avoids call ✅"| PL_SG["never calls<br/>spinner.Stop()"]
    end

    style PH_S fill:#ff4444,stroke:#000,color:#fff
    style PH_SG fill:#ff4444,stroke:#000,color:#fff
    style PH_E fill:#44aa44,stroke:#000,color:#fff
    style PH_EG fill:#44aa44,stroke:#000,color:#fff
    style PL_S fill:#44aa44,stroke:#000,color:#fff
    style PL_SG fill:#44aa44,stroke:#000,color:#fff

---

{
    name:      "successful push with only blob-digest responses (no status updates)",
    modelName: "test-model",
    serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
        "/api/push": func(w http.ResponseWriter, r *http.Request) {
            // All responses carry a digest — no status-only message,
            // so spinner is never created
            responses := []api.ProgressResponse{
                {Digest: "sha256:abc123def456", Total: 100, Completed: 50},
                {Digest: "sha256:abc123def456", Total: 100, Completed: 100},
                {Digest: "sha256:xyz789ghi012", Total: 100, Completed: 100},
            }
            for _, resp := range responses {
                json.NewEncoder(w).Encode(resp)
                w.(http.Flusher).Flush()
            }
        },
    },
    expectedOutput: "\nYou can find your model at:\n\n\thttps://ollama.com/test-model\n",
},

---

go test ./cmd/ -run TestPushHandler -v

---

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x...]

goroutine 1 [running]:
github.com/ollama/ollama/progress.(*Spinner).Stop(...)
    progress/spinner.go:75
github.com/ollama/ollama/cmd.PushHandler(...)
    cmd/cmd.go:995

---

p.Stop()
-spinner.Stop()
+if spinner != nil {
+    spinner.Stop()
+}

---

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x...]

goroutine 1 [running]:
github.com/ollama/ollama/progress.(*Spinner).Stop(...)
    progress/spinner.go:75
github.com/ollama/ollama/cmd.PushHandler(...)
    cmd/cmd.go:995
RAW_BUFFERClick to expand / collapse

What is the issue?

PushHandler can panic with nil pointer on spinner.Stop()

Found this while poking around cmd/cmd.go to understand the push flow.

What's happening

PushHandler declares spinner as nil at line 948:

var spinner *progress.Spinner  // nil

The only place it ever gets assigned is inside the progress callback fn (line 974), and only when the server sends a response with an empty Digest:

} else if status != resp.Status {
    ...
    spinner = progress.NewSpinner(status)  // <-- only assignment
}

So if the server only sends blob-digest responses (all with non-empty Digest), spinner stays nil the whole time.

The error path at line 983-985 handles this:

if err := client.Push(cmd.Context(), &request, fn); err != nil {
    if spinner != nil { spinner.Stop() }  // <-- guarded
    ...
}

But the success path right after it doesn't:

p.Stop()
spinner.Stop()  // <-- no nil check, will panic if nil

PullHandler (line 1417) doesn't call spinner.Stop() at all on success, which makes this look like an oversight rather than intentional.

Is it reachable right now?

Honestly, probably not in production. The server (PushModel in server/images.go:531) starts with {Status: "retrieving manifest"} which creates the spinner. So in practice there's always at least one status-only response.

But the guard should be there anyway — the error path has it, and there's nothing in the contract guaranteeing the server always sends a status response first.

Visuals

Spinner Lifecycle in PushHandler

flowchart TD
    A["PushHandler() called"] --> B["var spinner *progress.Spinner<br/>= nil"]
    B --> C["client.Push() with fn callback"]
    C --> D{"ctx err or<br/>Push err?"}

    D -->|"error"| E["error path<br/>if spinner != nil { spinner.Stop() } ✅"]
    E --> Z["return err"]

    D -->|"no error (success)"| F["p.Stop()"]
    F --> G["spinner.Stop()"]
    G --> H{"spinner == nil?"}

    H -->|"YES"| I["💥 PANIC<br/>nil pointer dereference"]
    H -->|"NO"| J["stop spinner normally ✅"]

    style I fill:#ff4444,stroke:#000,color:#fff
    style E fill:#44aa44,stroke:#000,color:#fff
    style J fill:#44aa44,stroke:#000,color:#fff

Where the spinner actually gets created

flowchart TD
    CB["fn callback receives ProgressResponse"] --> Q1{"resp.Digest != ''?"}

    Q1 -->|"YES (blob response)"| B1["if spinner != nil { spinner.Stop() }"]
    B1 --> B2["create/update bar for digest"]
    B2 --> RET["return nil"]

    Q1 -->|"NO (status response)"| Q2{"status != resp.Status?"}
    Q2 -->|"NO"| RET
    Q2 -->|"YES"| S1["if spinner != nil { spinner.Stop() }"]
    S1 --> S2["status = resp.Status"]
    S2 --> S3["spinner = progress.NewSpinner(status)"]
    S3 --> S4["p.Add(status, spinner)"]
    S4 --> RET

    S3 -.->|"🟢 only place spinner is assigned"| S3

Normal flow vs panic scenario

sequenceDiagram
    actor CLI as ollama push
    participant Server as Registry/Server
    participant Handler as PushHandler

    rect rgb(200, 255, 200)
        Note over CLI,Handler: Normal Production Flow
        CLI->>Server: POST /api/push
        Server-->>Handler: {Status: "retrieving manifest"}<br/>Digest = ""
        Note over Handler: status != "" → spinner created ✅
        Server-->>Handler: {Digest: "sha256:abc", ...}
        Note over Handler: Digest != "" → bar updated
        Server-->>Handler: {Status: "pushing manifest"}<br/>Digest = ""
        Note over Handler: status changed → spinner updated
        Server-->>Handler: {Status: "success"}
        Note over Handler: spinner.Stop() → safe ✅
    end

    rect rgb(255, 200, 200)
        Note over CLI,Handler: Edge case (no status-only responses)
        CLI->>Server: POST /api/push
        Server-->>Handler: {Digest: "sha256:abc", Total:100, Completed:50}<br/>Digest != ""
        Note over Handler: bar created, spinner still nil
        Server-->>Handler: {Digest: "sha256:abc", Total:100, Completed:100}<br/>Digest != ""
        Note over Handler: bar updated, spinner still nil
        Server-->>Handler: {Digest: "sha256:xyz", Total:100, Completed:100}<br/>Digest != ""
        Note over Handler: bar updated, spinner STILL nil
        Note over Handler: 💥 spinner.Stop() on nil → PANIC
    end

Inconsistency across handlers

flowchart LR
    subgraph PushHandler
        direction TB
        PH_E["Error path"] -->|"guarded ✅"| PH_EG["if spinner != nil {<br/>  spinner.Stop()<br/>}"]
        PH_S["Success path"] -->|"unguarded ❌"| PH_SG["spinner.Stop()"]
    end

    subgraph PullHandler
        direction TB
        PL_S["Success path"] -->|"avoids call ✅"| PL_SG["never calls<br/>spinner.Stop()"]
    end

    style PH_S fill:#ff4444,stroke:#000,color:#fff
    style PH_SG fill:#ff4444,stroke:#000,color:#fff
    style PH_E fill:#44aa44,stroke:#000,color:#fff
    style PH_EG fill:#44aa44,stroke:#000,color:#fff
    style PL_S fill:#44aa44,stroke:#000,color:#fff
    style PL_SG fill:#44aa44,stroke:#000,color:#fff

Repro

Added this test case in TestPushHandler — it panics reliably:

{
    name:      "successful push with only blob-digest responses (no status updates)",
    modelName: "test-model",
    serverResponse: map[string]func(w http.ResponseWriter, r *http.Request){
        "/api/push": func(w http.ResponseWriter, r *http.Request) {
            // All responses carry a digest — no status-only message,
            // so spinner is never created
            responses := []api.ProgressResponse{
                {Digest: "sha256:abc123def456", Total: 100, Completed: 50},
                {Digest: "sha256:abc123def456", Total: 100, Completed: 100},
                {Digest: "sha256:xyz789ghi012", Total: 100, Completed: 100},
            }
            for _, resp := range responses {
                json.NewEncoder(w).Encode(resp)
                w.(http.Flusher).Flush()
            }
        },
    },
    expectedOutput: "\nYou can find your model at:\n\n\thttps://ollama.com/test-model\n",
},

Run it:

go test ./cmd/ -run TestPushHandler -v

Output before fix:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x...]

goroutine 1 [running]:
github.com/ollama/ollama/progress.(*Spinner).Stop(...)
    progress/spinner.go:75
github.com/ollama/ollama/cmd.PushHandler(...)
    cmd/cmd.go:995

After fix, all 5 test cases pass.

Fix

 p.Stop()
-spinner.Stop()
+if spinner != nil {
+    spinner.Stop()
+}

Same pattern the error path already uses. Happy to open a PR.

Relevant log output

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x...]

goroutine 1 [running]:
github.com/ollama/ollama/progress.(*Spinner).Stop(...)
    progress/spinner.go:75
github.com/ollama/ollama/cmd.PushHandler(...)
    cmd/cmd.go:995

OS

macOS

GPU

Apple

CPU

Apple

Ollama version

v0.24.0

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

ollama - 💡(How to fix) Fix cmd: PushHandler panics after a successful push