This article was originally published in Japanese on Qiita.
Introduction
Hi, I'm Kohei Matsunobe. I'm a second-year student in the online (correspondence) program at ZEN University, studying to become an AI engineer.
Once I started running Kaggle notebooks (kernels) from my local machine with kaggle kernels push, one small thing kept eating my time: checking what was going on. Is it still running? Did it finish? Did it fail? And if it failed, where? Checking that in the browser or digging through log files every single time got old fast.
In this post, I'll share a script that calls kaggle kernels status and kaggle kernels output from Python and boils the status and log down to a ~7-line summary plus one CSV row. I'll also cover how I fixed garbled text (mojibake) on Windows, and how I made the same script usable both from a .bat file and from an AI agent.
Note: this post is not about submissions. It focuses purely on monitoring run status and summarizing logs.
How I used to do it
At first, I got by with a batch file like this:
@echo off
kaggle kernels status koheip/my-notebook
kaggle kernels output koheip/my-notebook -p .\out -o
notepad .\out\my-notebook.log
It worked, but it had a few problems:
- The logs are long, and scrolling through Notepad to find the error is painful
- Non-ASCII output (Japanese, in my case) got garbled in the console
- There was no record of "when, which notebook, and what status"
- When I asked an AI agent to check the log, it had to read the entire thing, which is wasteful
That last point was the big one. If you're handing something to an agent, a short text with only the information you need is much easier to work with.
What the CLI gives you
You only need two commands (official docs: Kaggle CLI documentation, kaggle.com/docs/api).
kaggle kernels status
Shows the status of the latest run. You pass the kernel as owner/kernel-slug.
kaggle kernels status koheip/my-notebook
The output is a single line (on failure, an extra Failure message: "..." line is added):
koheip/my-notebook has status "KernelWorkerStatus.COMPLETE"
Depending on the CLI version, you may see lowercase complete or the KernelWorkerStatus. prefix, so the script takes only the last word and lowercases it.
kaggle kernels output
Downloads the output files from the latest run. The main options:
| Option | Meaning |
|---|---|
-p, --path |
Destination folder |
-o, --force |
Overwrite existing files |
-q, --quiet |
Suppress progress output |
--file-pattern |
Only download files matching a regex |
The key point: along with the output files, it also saves the execution log as <kernel-slug>.log. In most cases, it's a JSON array of entries with stream_name (stdout / stderr), time, and data. That's what we'll read and summarize.
If your notebook produces large output files, narrowing things down with --file-pattern speeds it up.
Authentication
The Kaggle CLI can authenticate via the KAGGLE_API_TOKEN environment variable, ~/.kaggle/access_token, or the classic ~/.kaggle/kaggle.json, among others. I went with reading the token from a dedicated file and passing it as an environment variable only to the child process. That way, whether the script is called from a .bat file or from an agent, the token is loaded the same way.
The summary script
Save this as kaggle_status_summary.py. It only uses the standard library.
# kaggle_status_summary.py
import argparse
import csv
import json
import os
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
# Force stdout to UTF-8, even on Windows (Python 3.7+)
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
DEFAULT_TOKEN_FILE = Path.home() / ".config" / "kaggle_token.txt"
def build_env(token_file: Path) -> dict:
"""Read the token from a file and build env vars for the child process."""
env = os.environ.copy()
env["PYTHONIOENCODING"] = "utf-8"
env["PYTHONUTF8"] = "1"
if token_file.exists():
env["KAGGLE_API_TOKEN"] = token_file.read_text(encoding="utf-8").strip()
return env
def run(cmd: list[str], env: dict) -> str:
r = subprocess.run(
cmd, env=env, capture_output=True,
text=True, encoding="utf-8", errors="replace",
)
if r.returncode != 0:
raise RuntimeError(f"{' '.join(cmd)} failed: {r.stderr.strip()[:300]}")
return r.stdout
def get_status(kernel: str, env: dict) -> tuple[str, str]:
out = run(["kaggle", "kernels", "status", kernel], env)
m = re.search(r'has status "([^"]+)"', out)
status = m.group(1).split(".")[-1].lower() if m else "unknown"
f = re.search(r'Failure message: "(.*)"', out)
return status, (f.group(1) if f else "")
def read_log(log_path: Path) -> list[tuple[str, str]]:
"""Return a list of (stream_name, text). Fall back to plain text if it isn't JSON."""
raw = log_path.read_text(encoding="utf-8", errors="replace")
try:
items = json.loads(raw)
return [(i.get("stream_name", ""), str(i.get("data", ""))) for i in items]
except (json.JSONDecodeError, AttributeError, TypeError):
return [("", line) for line in raw.splitlines()]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("kernel", help="owner/kernel-slug")
ap.add_argument("--out", default="kaggle_out")
ap.add_argument("--csv", default="kaggle_status_log.csv")
ap.add_argument("--token-file", type=Path, default=DEFAULT_TOKEN_FILE)
args = ap.parse_args()
env = build_env(args.token_file)
status, failure = get_status(args.kernel, env)
slug = args.kernel.split("/")[1]
out_dir = Path(args.out) / slug
entries, files = [], []
if status in ("complete", "error", "cancelacknowledged"):
out_dir.mkdir(parents=True, exist_ok=True)
run(["kaggle", "kernels", "output", args.kernel,
"-p", str(out_dir), "-o", "-q"], env)
files = [p.name for p in out_dir.iterdir() if p.is_file()]
log_path = out_dir / f"{slug}.log"
if log_path.exists():
entries = read_log(log_path)
err_pat = re.compile(r"(Error|Exception|Traceback)")
stderr_n = sum(1 for s, _ in entries if s == "stderr")
err_lines = [t.strip() for _, t in entries if err_pat.search(t)]
last_line = next((t.strip() for _, t in reversed(entries) if t.strip()), "")
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# The "~7 line" summary
print(f"[kernel] {args.kernel}")
print(f"[checked] {now}")
print(f"[status] {status}" + (f" / {failure}" if failure else ""))
print(f"[log] {len(entries)} entries (stderr: {stderr_n})")
print(f"[errors] {len(err_lines)} lines")
print(f"[last_err] {err_lines[-1][:120] if err_lines else '-'}")
print(f"[tail] {last_line[:120] or '-'} / files: {len(files)}")
# Append one row to a CSV history (with BOM so Excel opens it cleanly)
csv_path = Path(args.csv)
new = not csv_path.exists()
with csv_path.open("a", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
if new:
w.writerow(["checked_at", "kernel", "status", "log_entries",
"stderr", "error_lines", "last_error", "files"])
w.writerow([now, args.kernel, status, len(entries), stderr_n,
len(err_lines), err_lines[-1][:200] if err_lines else "",
len(files)])
return 0 if status != "error" else 1
if __name__ == "__main__":
sys.exit(main())
Running it looks like this (values are just an example):
[kernel] koheip/my-notebook
[checked] 2026-10-31 15:42:10
[status] complete
[log] 214 entries (stderr: 12)
[errors] 0 lines
[last_err] -
[tail] Done. / files: 3
Three things I was deliberate about:
-
Exit code: it returns 1 when the status is
error, so it's easy to branch on withif errorlevel 1in a.batfile or on the agent side -
Status normalization: to absorb differences between CLI versions,
KernelWorkerStatus.COMPLETEbecomescomplete -
No download while running: for
running/queued, it only checks the status and exits, so it stays lightweight even if you call it often
That said, the Kaggle API has rate limits. If you monitor in a loop, leave a gap of a few minutes between checks.
Setting up the token file
Generate a token on your Kaggle settings page and save it as a one-line text file.
# PowerShell: create the token file (it should contain only the token, on one line)
New-Item -ItemType Directory -Force "$HOME\.config" | Out-Null
Set-Content -Path "$HOME\.config\kaggle_token.txt" -Value "YOUR_TOKEN_HERE" -Encoding ascii -NoNewline
I'm strict about two rules: never put this file under Git, and never paste it into an article or a chat. When an agent uses the script, all it needs is the script's path; it never has to see the token itself.
Fixing garbled text on Windows
On Windows, the console code page often isn't UTF-8 by default (on Japanese Windows it's CP932 / Shift_JIS). Kaggle logs, on the other hand, are UTF-8. If you do nothing, non-ASCII characters get garbled, or the script dies with a UnicodeEncodeError.
The fix is to switch to UTF-8 on both the console side and the Python side.
Calling from a .bat file
@echo off
chcp 65001 >nul
set PYTHONIOENCODING=utf-8
set PYTHONUTF8=1
python "%~dp0kaggle_status_summary.py" koheip/my-notebook
if errorlevel 1 echo [!] The run ended with an error
If your .bat file itself contains non-ASCII text, save it as UTF-8 so it displays correctly after chcp 65001.
Calling from PowerShell
chcp 65001 > $null
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONUTF8 = "1"
python .\kaggle_status_summary.py koheip/my-notebook
if ($LASTEXITCODE -ne 0) { Write-Host "[!] The run ended with an error" }
What the Python side does
Inside the script, I also do three things just to be safe:
-
sys.stdout.reconfigure(encoding="utf-8")to make stdout UTF-8 -
subprocess.run(..., encoding="utf-8", errors="replace")to read the child process output as UTF-8 - Pass
PYTHONIOENCODING=utf-8to the child process (the Kaggle CLI) as well
There are cases where chcp 65001 alone, or PYTHONIOENCODING alone, only fixes half of the problem. That tripped me up at first. Set both and you're good.
Wrap-up
-
kaggle kernels statusgives you the status;kaggle kernels outputgives you the output files and<slug>.log - Reading the log in Python and condensing it into a ~7-line summary plus one CSV row makes checking much easier
- Loading the token from a file and passing it to the child process means
.batfiles and agents use the same path - On Windows, use both
chcp 65001andPYTHONIOENCODING=utf-8to avoid garbled text
Compared to the days of scrolling through logs in Notepad, I now spend far less time checking runs, and I hand far less text to my agents. I hope this helps anyone else running Kaggle notebooks from their local machine!
Top comments (0)