-
Notifications
You must be signed in to change notification settings - Fork 0
[FIRE-1932] feat(logs): add surface (i.e. Claude CLI, Claude Desktop, etc) on every hook log line #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
amos-qualifire
wants to merge
3
commits into
feature/FIRE-1932/log-shipping
from
feature/FIRE-1932/log-surface-token
Open
[FIRE-1932] feat(logs): add surface (i.e. Claude CLI, Claude Desktop, etc) on every hook log line #36
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
847b759
[FIRE-1932] feat(logs): say which surface wrote each line
amos-qualifire 48fa59d
[FIRE-1932] test: Windows PowerShell 5.1 drops the empty -Surface arg…
amos-qualifire b226010
[FIRE-1932] test: a manual harness for running the working tree again…
amos-qualifire File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # The hook log line | ||
|
|
||
| Every Rogue plugin's hook dispatcher appends **one line per invocation** to its own | ||
| file under `~/.rogue/logs/` (`%USERPROFILE%\.rogue\logs\` on Windows) — | ||
| `claude.log`, `codex.log`, `cursor.log`, `gemini.log`, `copilot.log`, | ||
| `antigravity.log`. | ||
|
|
||
| ``` | ||
| 2026-08-13T07:02:05Z provider=claude surface=cli event=PreToolUse outcome=allow | ||
| ``` | ||
|
|
||
| Fields are `key=value`, separated by single spaces, in a fixed order. A reader | ||
| finds a value by scanning from `<key>=` to the next space, so **no value contains a | ||
| space or an `=`**. | ||
|
|
||
| | position | token | always present | notes | | ||
| | --- | --- | --- | --- | | ||
| | 1 | timestamp | yes | UTC ISO-8601, second precision, no fractional part | | ||
| | 2 | `provider=` | yes | the agent slug, which is also the file's basename | | ||
| | 3 | `surface=` | **no — optional** | which surface of that agent wrote the line | | ||
| | 4 | `event=` | yes | the vendor's own event name, verbatim casing | | ||
| | 5+ | free-form | yes | `outcome=`, `raw=`, `reason=`, … per dispatcher | | ||
|
|
||
| ## `surface=` — which surface wrote the line | ||
|
|
||
| There is **one log file per agent family per machine**, and every surface of that | ||
| family appends to it. Claude Code launched from the CLI, from the Desktop app and a | ||
| Cowork session all write into the same `claude.log`. The heartbeat knows the | ||
| surface, but it describes *one session*, while a log file holds lines from many | ||
| sessions across many surfaces — so the surface has to be stamped on each line as it | ||
| is written. | ||
|
|
||
| ### Vocabulary | ||
|
|
||
| Lowercase, no spaces, no `=`. A **closed list**: the token is one of these strings | ||
| and nothing else — never a path, a user name, a host name, or a window title. | ||
|
|
||
| | plugin (`provider=`) | `surface=` | how it is determined | | ||
| | --- | --- | --- | | ||
| | `claude` | `cli` | `CLAUDE_CODE_ENTRYPOINT` is set and matches nothing below | | ||
| | `claude` | `desktop` | `CLAUDE_CODE_ENTRYPOINT` contains `desktop` | | ||
| | `claude` | `cowork` | `CLAUDE_CODE_ENTRYPOINT` contains `cowork` | | ||
| | `codex` | `codex_cli` | `ROGUE_CODEX_SURFACE` unset, `codex_cli`, or unrecognised | | ||
| | `codex` | `codex_app` | `ROGUE_CODEX_SURFACE=codex_app` (the installer pins it) | | ||
| | `antigravity` | `antigravity` | the event's `transcriptPath` is under `…/antigravity/…` | | ||
| | `antigravity` | `antigravity_ide` | `transcriptPath` under `…/antigravity-ide/…` | | ||
| | `antigravity` | `antigravity_cli` | `transcriptPath` under `…/antigravity-cli/…` | | ||
| | `cursor` | `cursor` | single surface — a constant | | ||
| | `copilot` | `github_copilot` | single surface — a constant | | ||
| | `gemini` | `gemini_cli` | single surface — a constant | | ||
|
|
||
| Each plugin resolves this **once**, from the signal its heartbeat already uses, and | ||
| the two read one shared table: | ||
|
|
||
| - `plugins/rogue/scripts/surface.sh` / `.ps1` — `hook` takes the slug, `heartbeat` | ||
| takes the display label, from the same `case`. | ||
| - `plugins/codex/scripts/surface.sh` / `.ps1` — one read of `ROGUE_CODEX_SURFACE`, | ||
| validated against the closed list, feeding the log token, the `x-rogue-agent` | ||
| header and the heartbeat alike. | ||
| - Antigravity resolves it from the payload once per invocation and passes the same | ||
| value to its log line, its heartbeat and its per-surface enrichment. | ||
|
|
||
| A second, independent way to decide the surface would be worse than no token at | ||
| all: a line and the roster row for the same session could then name different | ||
| surfaces. | ||
|
|
||
| ### The token is OPTIONAL | ||
|
|
||
| It is **absent** when the surface cannot be determined. There is no | ||
| `surface=unknown` and no empty `surface=` — either would be indistinguishable from | ||
| a real value to a reader scanning `key=` tokens. | ||
|
|
||
| In practice it is absent in two cases: | ||
|
|
||
| 1. **Lines written by a plugin version older than the ones below.** Old lines are | ||
| never rewritten; they have no token, permanently. | ||
| 2. **An antigravity event whose payload carries no `transcriptPath`** — including | ||
| every line written before the payload is read, such as `outcome=unconfigured` on | ||
| a machine with no API key. `transcriptPath` is the only reliable signal (three | ||
| Antigravity products share one install, so a filesystem probe cannot tell which | ||
| is running), and guessing is worse than omitting. | ||
|
|
||
| The other five plugins determine their surface on every line they write. | ||
|
|
||
| ### First version that ships it | ||
|
|
||
| | plugin | version | | ||
| | --- | --- | | ||
| | rogue (Claude Code) | 1.0.24 | | ||
| | codex | 1.0.1 | | ||
| | cursor | 1.1.1 | | ||
| | copilot | 1.2.1 | | ||
| | antigravity | 1.0.24 | | ||
| | gemini | 1.0.25 | | ||
|
|
||
| ### Guarantees | ||
|
|
||
| - The three dispatchers of a plugin (POSIX `sh`, PowerShell, and Node for Gemini) | ||
| emit **the same token for the same event** — asserted by | ||
| `tests/test_hook_logs.sh`, `tests/test_hook_logs.ps1` and | ||
| `tests/test_hook_mjs.mjs`. | ||
| - Resolving a surface can never block a session, change an allow/deny outcome, or | ||
| write to stderr. Every resolution is guarded; a failure yields an empty slug, | ||
| which yields no token. | ||
| - The upload envelope is unchanged. This is a per-line change only. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 1.0.23 | ||
| 1.0.24 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ | |
| EVENT="" # hook event name, from $1 | ||
| PLUGIN_ROOT="" # <root> of this plugin, derived from $0 | ||
| BODY="" # the hook payload, as received then enriched | ||
| SURFACE="" # antigravity | antigravity_ide | antigravity_cli, or empty | ||
| URL="" # where to POST it | ||
| SUBAGENT_ID="" # set by reattribute_subagent when this event is a subagent's | ||
| SUBAGENT_NAME="" | ||
|
|
@@ -135,8 +136,13 @@ log() { | |
| ( umask 077 | ||
| mkdir -p "$(dirname "$ROGUE_LOG_FILE")" 2>/dev/null | ||
| rotate_log | ||
| printf '%s provider=antigravity event=%s %s\n' \ | ||
| "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$EVENT" "$*" >> "$ROGUE_LOG_FILE" 2>/dev/null ) | ||
| # `${SURFACE:+ surface=$SURFACE}` expands to NOTHING when the slug is empty - | ||
| # which is the normal case for an event whose payload carries no transcriptPath, | ||
| # and for every line written before read_body. Never `surface=`, never | ||
| # `surface=unknown`. | ||
| printf '%s provider=antigravity%s event=%s %s\n' \ | ||
| "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${SURFACE:+ surface=$SURFACE}" \ | ||
| "$EVENT" "$*" >> "$ROGUE_LOG_FILE" 2>/dev/null ) | ||
| } | ||
| sanitize() { printf '%s' "$1" | tr -d '\000-\037\177'; } | ||
|
|
||
|
|
@@ -634,7 +640,7 @@ maybe_heartbeat() { | |
| [ "$EVENT" = "PreInvocation" ] || return 0 | ||
| case "$BODY" in | ||
| *'"invocationNum":0'*|*'"invocationNum": 0'*) | ||
| _hb_agent=$(surface_from_transcript "$(json_field transcriptPath "$BODY")") | ||
| _hb_agent="$SURFACE" | ||
| ( nohup sh "${PLUGIN_ROOT}/scripts/heartbeat.sh" "$_hb_agent" >/dev/null 2>&1 & ) ;; | ||
| esac | ||
| } | ||
|
|
@@ -649,7 +655,7 @@ maybe_heartbeat() { | |
| # so the wait was pure latency on the event that blocks the developer. The | ||
| # prompt comes from the conversation store instead. | ||
| enrich_body() { | ||
| _surface=$(surface_from_transcript "$(json_field transcriptPath "$BODY")") | ||
| _surface="$SURFACE" | ||
|
Comment on lines
-652
to
+658
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure this is correct |
||
| if [ "$_surface" = "antigravity_ide" ]; then | ||
| case "$EVENT" in | ||
| # The pending prompt, before the model call that would consume it. | ||
|
|
@@ -736,6 +742,12 @@ main() { | |
| require_api_key # exits before stdin is read when there is no key | ||
| load_actor | ||
| read_body | ||
| # ONE resolution, three consumers: the log token, the heartbeat's roster agent | ||
| # and enrich_body's IDE-only branch. transcriptPath is the only reliable signal | ||
| # (three products share one install, so a filesystem probe cannot tell which is | ||
| # running), and it is absent from some events - which is exactly why the log | ||
| # token is optional. Empty here means the line carries no surface= at all. | ||
| SURFACE=$(surface_from_transcript "$(json_field transcriptPath "$BODY")") | ||
|
|
||
| maybe_heartbeat | ||
| # Re-attribute BEFORE enriching: augment_with_transcript re-closes the JSON | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.