{"slug": "where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads", "title": "Where Claude Code and Codex keep /goal state, and how one open-source app reads both", "summary": "An open-source desktop app called Kiteline reads `/goal` state from both Claude Code and Codex by parsing two different storage formats: Claude Code writes `goal_status` attachment lines into its JSONL session transcripts, while Codex keeps goals in a `thread_goals` table inside `goals_N.sqlite` under `CODEX_HOME`. The implementation, in `watcher/kiteline-watch.py`, normalizes both sources into a single shape consumed by the phone, watch and desktop UIs, and handles edge cases such as read-only SQLite connections, WAL-change polling, truncated transcripts and detecting when a goal has stalled.", "body_md": "Both Claude Code and Codex now have `/goal`: you give a session a condition that can be checked, and the agent keeps working round after round until the condition is met. It is a good fit for long, well-defined jobs such as \"get every unit test passing\" or \"first paint under 1.5 s and Lighthouse above 90\", the kind of task where an agent would otherwise stop halfway and write a confident summary.\n\nThe interesting part for tool builders is that the two agents record goals in completely different places. This post walks through both formats, using the implementation in [Kiteline](https://github.com/hon6/kiteline), an open-source desktop app (Mac, Windows, Linux) that follows Claude Code, Codex and other agent sessions and shows their progress on a phone. All the code mentioned below is in `watcher/kiteline-watch.py`.\n\n`goal_status` attachments in the transcript\nClaude Code writes each session as a JSONL transcript. Once a goal is set, extra lines appear with `type: \"attachment\"` and `attachment.type: \"goal_status\"`. The fields tell you what happened:\n\n| Line | Meaning | \n|---|---|\n| `{met: false, sentinel: true, condition}` | Goal set | \n| `{met: true, sentinel: true}` | Goal cleared ( `/goal clear` , or an unrecoverable error) | \n| `{met: false, condition, reason}` | One check: not met yet, `reason` says what is missing | \n| `{met: true, condition, reason, iterations, durationMs, tokens}` | Goal met, with rounds, time and tokens | \n| `{failed: true, …}` | The check decided the goal can't be met | \n\nThe check at the end of each round is itself a model call that judges the condition and explains why. Pauses show up as system lines instead, such as `Goal paused · <cause> · …` or `Goal still active · <cause> · retrying in N min`, which are easy to match:\n\n```\n_GOAL_PAUSED_RE = re.compile(r\"^Goal paused(?: after (\\d+) automatic retries)? · (.+?) · \")\n_GOAL_RETRY_RE = re.compile(r\"^Goal still active · (.+?) · retrying in (\\d+) min\")\n_GOAL_ERROR_RE = re.compile(r\"^Goal cleared after an unrecoverable error \\((.+?)\\)\")\n```\n\nA small state machine (`ClaudeGoal`) consumes these lines: the \"set\" line opens a goal, every check bumps `checks` and stores the reason, and met / failed / cleared change the state. If the transcript was truncated or resumed and the \"set\" line is missing, it picks up from the first check it sees.\n\n`thread_goals` table in local SQLite\nCodex does not put goals in the rollout file. They live in `goals_N.sqlite` under `CODEX_HOME`, one row per thread:\n\n```\nselect thread_id, objective, status, token_budget, tokens_used,\n       time_used_seconds, created_at_ms, updated_at_ms\nfrom thread_goals\n```\n\n`status` is one of `active`, `paused`, `blocked`, `usage_limited`, `budget_limited` or `complete`. Two details matter when you read this from another process:\n\n`sqlite3.connect(\"file:...?mode=ro\", uri=True)`) so you never compete with Codex for the write lock.`-wal` file. If neither changed, reuse the last result, so polling every second costs nothing. If the table doesn't exist (an older Codex), remember that until the file changes.\nBoth sources are turned into the same shape, and every UI (phone, watch, desktop) only reads that:\n\n```\n{\"text\": \"Retry failed payment callbacks automatically…\", \"state\": \"active\",\n \"checks\": 2, \"reason\": \"1 case still fails: a duplicate callback for the same order is booked twice\"}\n```\n\nOn the phone this becomes \"Goal · round 3\" in the session list, and a goal bar above the message box with the round, the time spent and the last check:\n\nReading the formats is easy. Deciding that a goal has *stopped* is not, and a wrong answer means a confusing push notification.\n\n`[Request interrupted by user]` marker) and otherwise waits for two minutes of idleness before calling a goal paused.`ClaudeGoal` now polls the file itself from a byte offset (starting over if the file was rewritten and got shorter), so the snapshot always has the latest state.\nThe full implementation (`ClaudeGoal`, `GoalBook`, `codex_goals`, `goal_command`) is in [hon6/kiteline](https://github.com/hon6/kiteline). Claude Code is a product of Anthropic and Codex is a product of OpenAI; Kiteline is an independent third-party tool.", "url": "https://wpnews.pro/news/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads", "canonical_source": "https://dev.to/hon6/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads-both-340a", "published_at": "2026-11-03 01:43:10+00:00", "updated_at": "2026-11-03 01:52:16.328638+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Kiteline", "Claude Code", "Codex", "Anthropic", "OpenAI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads", "markdown": "https://wpnews.pro/news/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads.md", "text": "https://wpnews.pro/news/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads.txt", "jsonld": "https://wpnews.pro/news/where-claude-code-and-codex-keep-goal-state-and-how-one-open-source-app-reads.jsonld"}}