Troubleshooting
When a link opens in the wrong place, Grinch can tell you which rule sent it there and why.
A link opened in the wrong place
Work through these in order. The commands assume the Grinch
alias from Get started.
1. Test the URL
--test runs a URL through your rules without opening a browser
and prints the rewritten URL, the target, and any launch arguments:
Grinch --test "https://github.com/jamtur01/grinch?utm_source=slack"
URL: https://github.com/jamtur01/grinch?utm_source=slack
Final: https://github.com/jamtur01/grinch
Browser: com.google.Chrome
Args: --profile-directory=Default
If the answer is wrong, the rules are the problem. If it is right but the
click still goes elsewhere, check that Grinch is the
default browser and that the rule
doesn't depend on the opener or modifier keys, which --test
can't reproduce.
2. List the loaded rules
--list-rules prints each rule's index, label, and target as
Grinch loaded them:
Grinch --list-rules
0: github.com → com.google.Chrome --profile-directory=Default
1: zoom.us/j/* → us.zoom.xos
Rules run top to bottom and the first match wins, so a broad rule above a
narrow one hides it. Give rules a name
to make the list easier to read.
3. Log real clicks
Set options: { logRequests: true }, reload the config, click
the link again, and choose Open Diagnostic Log. Each
resolve event records the opener app, the modifier keys, the
final URL, the target, and the rule that matched. Match
matchedRule.index against --list-rules.
Diagnostic log explains every field.
For a full trace on stderr, run the binary with
GRINCH_DEBUG=1. console.log calls in your config
also print to stderr.
Edits don't take effect
Grinch may be reading a different file. Choose Open Config from the menu to see which one it loaded. It uses the first file that exists in this order:
~/.grinch.js~/.config/grinch.js~/.config/grinch/grinch.js/Library/Application Support/Grinch/grinch.js, a system-wide fallback
Edit the active file, then choose Reload Config. If the
reload fails, the menu shows the error and Grinch keeps the last config that
loaded. --validate reports the same errors and warnings in a
terminal and exits 1 when the config doesn't load:
Grinch --validate
grinch: config OK — /Users/alex/.config/grinch.js
rules: 12
Grinch --validate
grinch: config invalid — SyntaxError: Unexpected identifier 'rules'. (line 4)
path: /Users/alex/.config/grinch.js
echo $?
1
From a terminal, kill -HUP <pid> also reloads the running
app's config. Grinch --version (or -V) shows its version.
Warnings don't fail validation. Rule and browser-target config warnings
print before the result and change it to config OK with warnings;
the exit status stays 0:
Grinch --validate
grinch: rules[0].profile is ignored; move it inside the browser target; this rule is suppressed
grinch: config OK with warnings — /Users/alex/.config/grinch.js
rules: 1
Profile warnings also print during validation, but do not change the result
to config OK with warnings.
Diagnostic log
Grinch writes one diagnostic log per launch, at:
~/Library/Logs/Grinch/Grinch_<timestamp>.log
It always records config warnings and load errors, JavaScript exceptions at runtime,
profile errors, and launch failures. logRequests adds a
resolve event for each routed link. The file is created on the
first event or when you choose Open Diagnostic Log.
Logs can contain complete URLs, including authentication tokens and personal data. Redact them before sharing. Report suspected vulnerabilities through the security policy.
Formats
The default is JSON, one object per line. Set
options: { logFormat: "text" } for readable text. A
resolve event in text format:
2026-05-11T12:57:25-0400 resolve
args: ["--profile-directory=Profile 10"]
browser: com.google.Chrome
final: https://example.com/
matchedRule:
index: 11
name: shift-override
modifiers:
command: false
control: false
option: false
shift: true
opener:
bundleId: com.tinyspeck.slackmacgap
name: Slack
pid: 731
rewritten: false
strategy: launch_new_instance
url: https://example.com/
The same event in JSON, which also carries a Unix timestamp:
{
"date": "2026-05-11T12:57:25-0400",
"event": "resolve",
"ts": 1778518645.634,
"url": "https://example.com/",
"final": "https://example.com/",
"rewritten": false,
"browser": "com.google.Chrome",
"args": ["--profile-directory=Profile 10"],
"strategy": "launch_new_instance",
"opener": {
"bundleId": "com.tinyspeck.slackmacgap",
"name": "Slack",
"pid": 731
},
"modifiers": {
"shift": true,
"option": false,
"command": false,
"control": false
},
"matchedRule": {"index": 11, "name": "shift-override"}
}
Fields
A resolve event records the routing decision; it does not
confirm the browser opened. launch_error events record invalid
URLs, missing browsers, and failures macOS reports after a launch request,
with url, browser, strategy, and
message; native errors also include errorDomain
and errorCode.
Config and JavaScript errors include the config path, when one was found, and the JavaScript message:
{"date":"2026-05-11T12:57:25-0400","event":"config_error","message":"SyntaxError (line 4)","path":"/Users/alex/.grinch.js","ts":1778518645.634}
2026-05-11T12:57:25-0400 config_error
message: SyntaxError (line 4)
path: /Users/alex/.grinch.js
Profile errors
profile_error events include the browser bundle ID, the
profile-data file path when available, and a message saying what to fix.
They report unreadable profile files, profile names Grinch couldn't
resolve, and profiles set on browsers that don't support them.
Errors that stop a launch affect the menu bar: an unresolved Chromium profile, an ambiguous or inaccessible Firefox profile, or a profile on a browser that doesn't support profiles. The first such error after a config load changes the icon to ⚠️ and shows Profile error: in the menu, including errors found when a dynamic target runs on a click. Hover over it for the full message. A config error takes precedence in the menu, and a successful reload clears the profile warning unless the error happens again.
Rotation, format changes, and early errors
Rotation (logRotateBytes,
logRotateDays) and logFormat take effect
once the config's JavaScript has run and its options are parsed, even if
Grinch later fails to build the routing engine. Errors before the first
config's options are parsed are written as JSON.
Changing logFormat starts a new file and leaves earlier
events in the previous one. Files created by format changes use suffixes
such as .1.log and .2.log in place of
.log. Open Diagnostic Log always opens the
current file. Launch errors use the same file and rotation settings,
including after config reloads.
console output stays on stderr with its
grinch [level]: prefix and is not copied into the log.
Chrome opens a link in a new window
When a rule selects a Chrome profile, Chrome can open a new window even if that profile already has a normal window. This can happen if you most recently used a Chrome web app, popup, or DevTools window for that profile.
To reuse an existing window, activate an ordinary Chrome window for that profile before opening the link. This is tracked in a Chromium issue.
Command-line reference
The binary is /Applications/Grinch.app/Contents/MacOS/Grinch.
It accepts:
| Flag | Effect |
|---|---|
--version, -V | Print the version. |
--test <url> | Run a URL through the rules without opening it. grinch: URLs are unwrapped, so --test grinch:tel:+15551234567 tests routing for tel:+15551234567. Exits 0 when the config loads, 1 if loading fails, or 2 for a missing URL or non-UTF-8 argument. |
--list-rules | Print rule indices, labels, and targets. Exits 0 when the config loads, 1 if loading fails, or 2 for a non-UTF-8 argument. |
--list-browsers | List every app registered to open https:// URLs: bundle ID and display name. |
--validate | Load the config and report whether it parses. Exits 0 on success, including when it prints warnings, and 1 on a load error, printing the message and the path it read. Use it in editor save hooks and CI. |
--bench N <url> | Resolve a URL N times in-process and report timing. |
--bench-init [N] | Load the config N times and report timing. Defaults to 1,000 loads. |
Except for --version, these commands exit 2 for non-UTF-8
arguments. --bench also exits 2 for missing arguments or a
non-positive iteration count; --bench-init exits 2 for an
invalid iteration count.