Conversation
Adds a hook that runs when goose's status changes between 'waiting' (ready for input) and 'thinking' (processing). This is useful for users running multiple goose sessions who want visual indicators of which sessions are ready for input. Example usage for tmux title: export GOOSE_STATUS_HOOK='printf "\033]2;goose: $1\033\\"' Features: - Cross-platform support (sh -c on Unix, cmd /C on Windows) - Runs asynchronously to not block the main thread - Errors are silently ignored Signed-off-by: Trae Robrock <trobrock@gmail.com>
Signed-off-by: Trae Robrock <trobrock@gmail.com>
Remove the test functions for run_status_hook as they are not needed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds status hook functionality to notify external scripts when Goose transitions between "waiting" (ready for input) and "thinking" (processing) states, continuing work from PR #6702. This enables use cases like updating tmux window indicators to show session status.
Changes:
- Adds
run_status_hook()function that spawns a thread to execute a user-configured hook script - Calls the hook with "waiting" status before prompting for user input
- Calls the hook with "thinking" status before processing agent responses
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/goose-cli/src/session/output.rs | Adds run_status_hook() function that executes GOOSE_STATUS_HOOK script with status argument via shell command |
| crates/goose-cli/src/session/mod.rs | Integrates status hook calls into interactive loop at appropriate state transitions |
| #[cfg(target_os = "windows")] | ||
| let result = std::process::Command::new("cmd") | ||
| .arg("/C") | ||
| .arg(format!("{} {}", hook, status)) | ||
| .stdin(std::process::Stdio::null()) | ||
| .stdout(std::process::Stdio::null()) | ||
| .stderr(std::process::Stdio::null()) | ||
| .status(); | ||
|
|
||
| #[cfg(not(target_os = "windows"))] | ||
| let result = std::process::Command::new("sh") | ||
| .arg("-c") | ||
| .arg(format!("{} {}", hook, status)) | ||
| .stdin(std::process::Stdio::null()) | ||
| .stdout(std::process::Stdio::null()) | ||
| .stderr(std::process::Stdio::null()) | ||
| .status(); | ||
|
|
There was a problem hiding this comment.
Execute the hook directly without a shell to avoid potential injection. Use Command::new(&hook).arg(&status) instead of passing through sh -c. This is safer and simpler.
| #[cfg(target_os = "windows")] | |
| let result = std::process::Command::new("cmd") | |
| .arg("/C") | |
| .arg(format!("{} {}", hook, status)) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); | |
| #[cfg(not(target_os = "windows"))] | |
| let result = std::process::Command::new("sh") | |
| .arg("-c") | |
| .arg(format!("{} {}", hook, status)) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); | |
| let result = std::process::Command::new(&hook) | |
| .arg(&status) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); |
| #[cfg(target_os = "windows")] | ||
| let result = std::process::Command::new("cmd") | ||
| .arg("/C") | ||
| .arg(format!("{} {}", hook, status)) | ||
| .stdin(std::process::Stdio::null()) | ||
| .stdout(std::process::Stdio::null()) | ||
| .stderr(std::process::Stdio::null()) | ||
| .status(); | ||
|
|
||
| #[cfg(not(target_os = "windows"))] | ||
| let result = std::process::Command::new("sh") | ||
| .arg("-c") | ||
| .arg(format!("{} {}", hook, status)) | ||
| .stdin(std::process::Stdio::null()) | ||
| .stdout(std::process::Stdio::null()) | ||
| .stderr(std::process::Stdio::null()) | ||
| .status(); | ||
|
|
There was a problem hiding this comment.
Execute the hook directly without a shell to avoid potential injection. Use Command::new(&hook).arg(&status) instead of passing through cmd /C. This is safer and simpler.
| #[cfg(target_os = "windows")] | |
| let result = std::process::Command::new("cmd") | |
| .arg("/C") | |
| .arg(format!("{} {}", hook, status)) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); | |
| #[cfg(not(target_os = "windows"))] | |
| let result = std::process::Command::new("sh") | |
| .arg("-c") | |
| .arg(format!("{} {}", hook, status)) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); | |
| let result = std::process::Command::new(&hook) | |
| .arg(&status) | |
| .stdin(std::process::Stdio::null()) | |
| .stdout(std::process::Stdio::null()) | |
| .stderr(std::process::Stdio::null()) | |
| .status(); |
* 'main' of github.com:block/goose: (62 commits) Swap canonical model from openrouter to models.dev (#6625) Hook thinking status (#6815) Fetch new skills hourly (#6814) copilot instructions: Update "No prerelease docs" instruction (#6795) refactor: centralize audience filtering before providers receive messages (#6728) update doc to remind contributors to activate hermit and document minimal npm and node version (#6727) nit: don't spit out compaction when in term mode as it fills up the screen (#6799) fix: correct tool support detection in Tetrate provider model fetching (#6808) Session manager fixes (#6809) fix(desktop): handle quoted paths with spaces in extension commands (#6430) fix: we can default gooseignore without writing it out (#6802) fix broken link (#6810) docs: add Beads MCP extension tutorial (#6792) feat(goose): add support for AWS_BEARER_TOKEN_BEDROCK environment variable (#6739) [docs] Add OSS Skills Marketplace (#6752) feat: make skills available in codemode (#6763) Fix: Recipe Extensions Not Loading in Desktop (#6777) Different approach to determining final confidence level of prompt injection evaluation outcomes (#6729) fix: read_resource_tool deadlock causing test_compaction to hang (#6737) Upgrade error handling (#6747) ...
* main: docs: usage data collection (#6822) feat: platform extension migrator + code mode rename (#6611) feat: CLI flag to skip loading profile extensions (#6780) Swap canonical model from openrouter to models.dev (#6625) Hook thinking status (#6815) Fetch new skills hourly (#6814) copilot instructions: Update "No prerelease docs" instruction (#6795) refactor: centralize audience filtering before providers receive messages (#6728) update doc to remind contributors to activate hermit and document minimal npm and node version (#6727) nit: don't spit out compaction when in term mode as it fills up the screen (#6799) fix: correct tool support detection in Tetrate provider model fetching (#6808) Session manager fixes (#6809) fix(desktop): handle quoted paths with spaces in extension commands (#6430) fix: we can default gooseignore without writing it out (#6802) fix broken link (#6810) docs: add Beads MCP extension tutorial (#6792) feat(goose): add support for AWS_BEARER_TOKEN_BEDROCK environment variable (#6739)
Signed-off-by: Trae Robrock <trobrock@gmail.com> Co-authored-by: Trae Robrock <trobrock@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Trae Robrock <trobrock@gmail.com> Co-authored-by: Trae Robrock <trobrock@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Summary
continues the work from:
#6702