From 0c216beb4fdf77d193d89a046f5488b29e75f04b Mon Sep 17 00:00:00 2001 From: Md Mushfiqur Rahim <20mahin2020@gmail.com> Date: Fri, 22 May 2026 06:08:20 +0000 Subject: [PATCH 1/3] fix: close DEBUG_IMAP file descriptor on shutdown --- fetcher/fetcher.go | 7 +++++++ main.go | 1 + 2 files changed, 8 insertions(+) diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index c0aa5132..3efafab6 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -59,6 +59,13 @@ func getDebugIMAPWriter() io.Writer { return nil } +// CloseDebugFiles cleans up debug file handles opened during the session. +func CloseDebugFiles() { + if debugIMAPFile != nil { + debugIMAPFile.Close() + } +} + // Attachment holds data for an email attachment. type Attachment struct { Filename string diff --git a/main.go b/main.go index 5d54f53b..c740c454 100644 --- a/main.go +++ b/main.go @@ -3947,6 +3947,7 @@ func main() { args, level := parseGlobalFlags(os.Args) os.Args = args loglevel.Set(level) + defer fetcher.CloseDebugFiles() // If invoked with version flag, print version and exit if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "--version" || os.Args[1] == "version") { From 10133b04797e4f3cb86128408e698ff310f23aa6 Mon Sep 17 00:00:00 2001 From: FromSi Date: Sun, 24 May 2026 22:21:04 +0500 Subject: [PATCH 2/3] fix(fetcher): close debug files --- fetcher/fetcher.go | 5 ++- main.go | 87 ++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index 3efafab6..a6e58fe8 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -62,7 +62,10 @@ func getDebugIMAPWriter() io.Writer { // CloseDebugFiles cleans up debug file handles opened during the session. func CloseDebugFiles() { if debugIMAPFile != nil { - debugIMAPFile.Close() + if err := debugIMAPFile.Close(); err != nil { + loglevel.Debugf("debug IMAP close error: %v", err) + } + debugIMAPFile = nil } } diff --git a/main.go b/main.go index c740c454..bb3272b9 100644 --- a/main.go +++ b/main.go @@ -3405,14 +3405,14 @@ func runOAuthCLI(args []string) { fmt.Fprintln(os.Stderr, "Credentials are stored per provider in:") fmt.Fprintln(os.Stderr, " Gmail: ~/.config/matcha/oauth_client.json") fmt.Fprintln(os.Stderr, " Outlook: ~/.config/matcha/oauth_client_outlook.json") - os.Exit(1) + exit(1) } // Find the Python script and pass through to it script, err := config.OAuthScriptPath() if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) + exit(1) } cmdArgs := append([]string{script}, args...) @@ -3423,10 +3423,10 @@ func runOAuthCLI(args []string) { if err := cmd.Run(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { - os.Exit(exitErr.ExitCode()) + exit(exitErr.ExitCode()) } fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) + exit(1) } } @@ -3473,13 +3473,13 @@ func runSendCLI(args []string) { } if err := fs.Parse(args); err != nil { - os.Exit(1) + exit(1) } if *to == "" || *subject == "" { fmt.Fprintln(os.Stderr, "Error: --to and --subject are required") fs.Usage() - os.Exit(1) + exit(1) } // Read body from stdin if "-" @@ -3488,7 +3488,7 @@ func runSendCLI(args []string) { data, err := io.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err) - os.Exit(1) + exit(1) } emailBody = string(data) } @@ -3497,11 +3497,11 @@ func runSendCLI(args []string) { cfg, err := config.LoadConfig() if err != nil { fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err) - os.Exit(1) + exit(1) } if !cfg.HasAccounts() { fmt.Fprintln(os.Stderr, "Error: no accounts configured. Run matcha to set up an account first.") - os.Exit(1) + exit(1) } // Resolve account @@ -3519,7 +3519,7 @@ func runSendCLI(args []string) { } if account == nil { fmt.Fprintf(os.Stderr, "Error: no account found matching %q\n", *from) - os.Exit(1) + exit(1) } } else { account = cfg.GetFirstAccount() @@ -3564,7 +3564,7 @@ func runSendCLI(args []string) { fileData, err := os.ReadFile(attachPath) if err != nil { fmt.Fprintf(os.Stderr, "Error reading attachment %s: %v\n", attachPath, err) - os.Exit(1) + exit(1) } attachMap[filepath.Base(attachPath)] = fileData } @@ -3577,7 +3577,7 @@ func runSendCLI(args []string) { rawMsg, sendErr := sender.SendEmail(account, recipients, ccList, bccList, *subject, emailBody, string(htmlBody), images, attachMap, "", nil, *signSMIME, *encryptSMIME, *signPGP, false) if sendErr != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", sendErr) - os.Exit(1) + exit(1) } // Append to Sent folder via IMAP (Gmail auto-saves, so skip it) @@ -3943,11 +3943,15 @@ func parseGlobalFlags(args []string) ([]string, loglevel.Level) { return filtered, level } +func exit(code int) { + fetcher.CloseDebugFiles() + os.Exit(code) +} + func main() { args, level := parseGlobalFlags(os.Args) os.Args = args loglevel.Set(level) - defer fetcher.CloseDebugFiles() // If invoked with version flag, print version and exit if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "--version" || os.Args[1] == "version") { @@ -3959,53 +3963,53 @@ func main() { fmt.Printf(" built on %s", date) } fmt.Println() - os.Exit(0) + exit(0) } // If invoked as CLI update command, run updater and exit. if len(os.Args) > 1 && os.Args[1] == "update" { if err := runUpdateCLI(); err != nil { fmt.Fprintf(os.Stderr, "update failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } // Daemon CLI subcommand: matcha daemon if len(os.Args) > 1 && os.Args[1] == "daemon" { runDaemonCLI(os.Args[2:]) - os.Exit(0) + exit(0) } // OAuth2 CLI subcommand: matcha oauth [flags] // "gmail" is kept as an alias for backwards compatibility. if len(os.Args) > 1 && (os.Args[1] == "oauth" || os.Args[1] == "gmail") { runOAuthCLI(os.Args[2:]) - os.Exit(0) + exit(0) } // Send email CLI subcommand: matcha send --to --subject [flags] if len(os.Args) > 1 && os.Args[1] == "send" { runSendCLI(os.Args[2:]) - os.Exit(0) + exit(0) } // Install plugin CLI subcommand: matcha install if len(os.Args) > 1 && os.Args[1] == "install" { if err := matchaCli.RunInstall(os.Args[2:]); err != nil { fmt.Fprintf(os.Stderr, "install failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } // Config CLI subcommand: matcha config [plugin_name] if len(os.Args) > 1 && os.Args[1] == "config" { if err := matchaCli.RunConfig(os.Args[2:]); err != nil { fmt.Fprintf(os.Stderr, "config failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } // Contacts CLI subcommand: matcha contacts [flags] @@ -4014,15 +4018,15 @@ func main() { case "export": if err := matchaCli.RunContactsExport(os.Args[3:]); err != nil { fmt.Fprintf(os.Stderr, "contacts export failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) case "sync": if err := matchaCli.RunContactsSync(os.Args[3:]); err != nil { fmt.Fprintf(os.Stderr, "contacts sync failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } } @@ -4030,9 +4034,9 @@ func main() { if len(os.Args) > 1 && os.Args[1] == "setup-mailto" { if err := matchaCli.SetupMailto(); err != nil { fmt.Fprintf(os.Stderr, "setup-mailto failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } // Marketplace TUI subcommand: matcha marketplace @@ -4041,9 +4045,9 @@ func main() { p := tea.NewProgram(mp) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "marketplace failed: %v\n", err) - os.Exit(1) + exit(1) } - os.Exit(0) + exit(0) } // Migrate cache files from ~/.config/matcha/ to ~/.cache/matcha/ if needed @@ -4139,11 +4143,12 @@ func main() { if _, err := p.Run(); err != nil { plugins.Close() fmt.Printf("Alas, there's been an error: %v", err) - os.Exit(1) + exit(1) } plugins.CallHook(plugin.HookShutdown) plugins.Close() + fetcher.CloseDebugFiles() } func runDaemonCLI(args []string) { @@ -4155,7 +4160,7 @@ func runDaemonCLI(args []string) { fmt.Println(" stop Stop the running daemon") fmt.Println(" status Show daemon status") fmt.Println(" run Run the daemon in the foreground") - os.Exit(1) + exit(1) } switch args[0] { @@ -4169,7 +4174,7 @@ func runDaemonCLI(args []string) { runDaemonRun() default: fmt.Fprintf(os.Stderr, "unknown daemon command: %s\n", args[0]) - os.Exit(1) + exit(1) } } @@ -4184,7 +4189,7 @@ func runDaemonStart() { exe, err := os.Executable() if err != nil { fmt.Fprintf(os.Stderr, "cannot find executable: %v\n", err) - os.Exit(1) + exit(1) } cmd := exec.Command(exe, "daemon", "run") @@ -4197,7 +4202,7 @@ func runDaemonStart() { if err := cmd.Start(); err != nil { fmt.Fprintf(os.Stderr, "failed to start daemon: %v\n", err) - os.Exit(1) + exit(1) } fmt.Printf("Daemon started (PID %d)\n", cmd.Process.Pid) @@ -4214,12 +4219,12 @@ func runDaemonStop() { process, err := os.FindProcess(pid) if err != nil { fmt.Fprintf(os.Stderr, "cannot find process %d: %v\n", pid, err) - os.Exit(1) + exit(1) } if err := process.Signal(os.Interrupt); err != nil { fmt.Fprintf(os.Stderr, "failed to stop daemon: %v\n", err) - os.Exit(1) + exit(1) } fmt.Printf("Daemon stopped (PID %d)\n", pid) @@ -4242,7 +4247,7 @@ func runDaemonStatus() { status, err := client.Status() if err != nil { fmt.Fprintf(os.Stderr, "failed to get status: %v\n", err) - os.Exit(1) + exit(1) } fmt.Printf("Daemon running (PID %d)\n", status.PID) @@ -4257,13 +4262,13 @@ func runDaemonRun() { cfg, err := config.LoadConfig() if err != nil { fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err) - os.Exit(1) + exit(1) } d := matchaDaemon.New(cfg) if err := d.Run(); err != nil { fmt.Fprintf(os.Stderr, "daemon error: %v\n", err) - os.Exit(1) + exit(1) } } From 1750e411982c699d860d9440a073c6598996a644 Mon Sep 17 00:00:00 2001 From: FromSi Date: Sun, 24 May 2026 22:29:05 +0500 Subject: [PATCH 3/3] fix(fetcher): refine close error log --- fetcher/fetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index 5a751bd5..6e825248 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -68,7 +68,7 @@ func getDebugIMAPWriter() io.Writer { func CloseDebugFiles() { if debugIMAPFile != nil { if err := debugIMAPFile.Close(); err != nil { - loglevel.Debugf("debug IMAP close error: %v", err) + loglevel.Debugf("IMAP file close error: %v", err) } debugIMAPFile = nil }