jesseduffield / jesseduffield/lazygit
Cached PR title with leading newline can corrupt state.yml
- Dominant language
- Go
- Stars
- 82.4k
- Forks
- 3k
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 19
Description
### Describe the bug
Lazygit can write a generated `state.yml` that it cannot read back on the next launch when a cached GitHub pull request title begins with a newline.
Startup then fails before the UI opens with an error like:
```text
yaml: line 79: did not find expected key
```
The invalid state was in the generated GitHub PR cache under `githubPullRequests`:
```yaml
githubPullRequests:
/path/to/repo:
- headRefName: example-branch
number: 9
title: |4-
Example pull request title
state: MERGED
url: https://github.com/example/example/pull/9
headRepositoryOwner: example
```
Removing `~/.local/state/lazygit/state.yml` lets lazygit start again, but the problem can recur if the cache is written with the same title shape.
### To Reproduce
This reproduces with the same `gopkg.in/yaml.v3` marshal/unmarshal path used for app state:
```go
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type State struct {
GithubPullRequests map[string][]PR `yaml:"githubPullRequests"`
}
type PR struct {
HeadRefName string `yaml:"headRefName"`
Number int `yaml:"number"`
Title string `yaml:"title"`
State string `yaml:"state"`
}
func main() {
state := State{GithubPullRequests: map[string][]PR{
"/tmp/repo": {{
HeadRefName: "example-branch",
Number: 9,
Title: "\nExample pull request title",
State: "MERGED",
}},
}}
b, err := yaml.Marshal(state)
fmt.Println(err)
fmt.Print(string(b))
var out State
fmt.Println(yaml.Unmarshal(b, &out))
}
```
Output:
```text
githubPullRequests:
/tmp/repo:
- headRefName: example-branch
number: 9
title: |4-
Example pull request title
state: MERGED
yaml: line 2: did not find expected key
```
I also reproduced this against current `master` with a small regression test that:
1. Creates an `AppState` with a `CachedPullRequest.Title` beginning with `\n`.
2. Marshals it with `yaml.Marshal`.
3. Unmarshals it back into `AppState`.
The unmarshal fails.
### Expected behavior
Lazygit should not persist generated app state that prevents the next launch.
A possible fix is to normalize cached PR titles before app state serialization, or otherwise avoid the `yaml.v3` leading-newline block scalar output for this field.
### Version info
```text
commit=v0.61.1, build date=2026-04-13T21:15:08Z, build source=binaryRelease, version=0.61.1, os=linux, arch=amd64, git version=2.54.0
```
I also checked current latest release, which is `v0.61.1`.
### Terminal info
Linux shell/tmux environment.
This does not appear to be terminal-rendering related; startup fails while parsing `state.yml`.
### Additional context
The path involved appears to be:
- `pkg/gui/controllers/helpers/refresh_helper.go` copies `pr.Title` into `config.CachedPullRequest.Title`
- `pkg/config/app_config.go` writes `AppState` using `yaml.Marshal(c.appState)`
- the next launch reads that file using `yaml.Unmarshal(appStateBytes, appState)`
I tested one small local patch that made the regression test pass:
```go
func (c CachedPullRequest) MarshalYAML() (interface{}, error) {
type cachedPullRequest CachedPullRequest
result := cachedPullRequest(c)
result.Title = strings.TrimLeft(result.Title, "\r\n")
return result, nil
}
```
That may not be the preferred upstream fix, but it prevents this specific cache field from making `state.yml` unreadable.
I saw the current contributing note about opening an issue before proposing a PR. Happy to provide the regression test/patch if useful.
Contributor guide
Assessment
This issue has not been assessed yet.