← Back to Blog

How to Find Undocumented Config Options by Reading a Binary

When a CLI tool’s documentation and config file don’t have the option you need, you can read the compiled binary with the Unix strings command. strings /path/to/binary | grep toml extracts readable text embedded in the binary, including config field names and struct tags. This technique revealed an undocumented args field in agent-deck’s config that accepts startup flags like --use-chrome. The escalation path: docs, help flag, config file, then read the binary.

The docs say nothing. The help flag says nothing. The config file doesn’t have the option you want. You’ve hit a wall.

There’s one more place to look: the binary itself.

This is a story about finding a hidden config option in agent-deck (a tool for running AI agent sessions) by reading its compiled binary with one command. But the technique works on any compiled CLI tool. You don’t need to be a developer to use it.

What a binary is

When developers write software, they write it in a human-readable language like Go, Rust, or C. Before you can run it, that code gets compiled. Compiled means a program called a compiler reads the source code and translates it into machine code: raw instructions for your CPU, packed into a single executable file.

That file is the binary. When you run claude or git or node, you’re running a binary.

Binaries aren’t meant to be read by humans. They’re mostly opaque. But they’re not completely opaque. Text strings that the program uses, like error messages, config field names, and help text, often get embedded into the binary as-is. They survive the compilation step.

That’s the crack in the wall.

The strings command

strings is a Unix tool that reads a file (any file, binary or not) and extracts every sequence of printable characters above a minimum length. It ignores the machine code it can’t understand and just prints the readable text.

Run it on a binary and you get a wall of output: help text, error messages, version numbers, log lines, URLs, and, if you’re lucky, config field names.

strings /path/to/your-binary

On its own, it’s a flood. You pipe it through grep to filter for what you care about:

strings /path/to/your-binary | grep toml

grep filters lines. | (the pipe) takes the output of the left command and feeds it as input to the right command. So this reads: “extract all strings from the binary, then show me only the lines that contain the word toml.”

What TOML is (and JSON, while we’re at it)

TOML and JSON are both formats for writing configuration files. A config file is just a text file your program reads on startup to know how it should behave.

JSON looks like this:

{
  "use_chrome": true,
  "timeout": 30
}

TOML looks like this:

use_chrome = true
timeout = 30

Both mean the same thing to a program. TOML tends to be the format for config files you edit by hand. JSON tends to be the format for data your program stores and reads programmatically (like session state).

In Go (the language agent-deck is written in), developers annotate their data structures with “struct tags” that say “this field, when reading a JSON file, looks for the key use_chrome” and “when reading a TOML file, looks for the key args.” Those annotations are text, and they get embedded in the binary.

The actual problem

The request was simple: make Chrome the default browser for every new agent-deck session. Right now, you have to toggle it on manually each time.

The obvious places to check:

  1. The docs: nothing about a global Chrome default
  2. agent-deck --help: nothing
  3. The config file (~/.agent-deck/config.toml): no Chrome field

So the investigation went one level deeper.

Reading the binary

agent-deck is a Go program. Go programs embed their struct tags as text. If there’s a config field for Chrome behavior, its TOML tag would be in the binary.

strings $(which agent-deck) | grep toml

This piped the binary through strings, then filtered for lines containing “toml.” The output included a line that looked like:

toml:"args"

That’s a struct tag. It means there’s a Go struct somewhere in agent-deck’s source code with a field that maps to the TOML key args. That’s the backdoor into the config.

Meanwhile, the field that controls Chrome behavior, use_chrome, only had a JSON tag:

json:"use_chrome"

No TOML tag. That means the developers stored it per-session in session data (JSON), but never wired it up to the global config file (TOML). You can’t set it globally, at least not directly.

The args field, on the other hand, passes additional arguments to agent-deck at startup. And one of those arguments is the flag that enables Chrome. So the config entry looks like this:

args = ["--use-chrome"]

That goes in your global config file. Every new session picks it up.

The two-layer fix

Getting Chrome on by default actually needed two separate solutions.

Layer 1: Future sessions. Add the args line to ~/.agent-deck/config.toml. Done. Every new session starts with Chrome enabled.

Layer 2: Existing sessions. Sessions already created don’t use the config file. They have their settings stored in a SQLite database.

SQLite is a database format that lives in a single file on your computer. Programs use it to store structured data without needing a separate database server. agent-deck stores session state this way.

You can query and update SQLite databases directly from the command line:

sqlite3 ~/.agent-deck/sessions.db "UPDATE sessions SET use_chrome = 1"

That updates every existing session at once. You could also run it as a bash loop across multiple database files, one per profile:

for db in ~/.agent-deck/profiles/*/sessions.db; do
  sqlite3 "$db" "UPDATE sessions SET use_chrome = 1"
done

In this case, there were 37 sessions across 3 profiles. One command updated all of them.

The escalation path

This is the part worth keeping. When you can’t find something in a CLI tool, there’s a standard escalation order:

  1. Check the docs. Official documentation, README, man page. Most things are here.
  2. Check the help output. --help and -h often have options the main docs miss.
  3. Check the config file. Look for the tool’s config file and read through the available keys.
  4. Read the binary. strings binary | grep <thing-you're-looking-for>

Step 4 is the one most people don’t know about. It’s not always going to give you what you need, but it costs 30 seconds to try, and sometimes you find exactly what you’re looking for.

The key thing to search for:

  • grep toml to find TOML config keys
  • grep json to find JSON config keys
  • grep config to find anything mentioning config
  • grep env to find environment variable names

If the program reads it, there’s a good chance the field name is in the binary.

What this isn’t

This isn’t hacking. You’re not breaking anything, exploiting anything, or doing anything the developers didn’t build in. You’re reading text that’s already there.

Think of it like reading ingredient labels. The manufacturer didn’t put the label there for you to reverse-engineer the recipe. But the ingredients are listed, and you can read them.

The args field exists. The developers built it. They just didn’t document it. Reading the binary told you it was there.

Further reading

Common Questions

How do I find undocumented config options in a CLI tool?

Use the strings command to extract readable text from the compiled binary: strings $(which tool-name) | grep config. Text strings like config field names, error messages, and environment variable names survive compilation. Filter with grep for what you’re looking for.

What is the strings command?

strings is a Unix tool that extracts printable character sequences from any file, including compiled binaries. It ignores machine code and prints only readable text. Piping through grep filters the output to relevant lines. Available by default on Mac and Linux.

What is a binary file?

A binary is a compiled executable file containing machine code. When developers write software in languages like Go or Rust, a compiler translates it into CPU instructions packed into a single file. Text strings used by the program (error messages, config keys) are often embedded as-is.

How do I update SQLite database settings from the terminal?

Use sqlite3 /path/to/database.db "UPDATE tablename SET field = value". This runs SQL directly against the database file. For batch updates across multiple databases, wrap the command in a bash for loop.


A note from Alex: hi i’m alex - i run code for creatives. i’m a writer so i feel that it is important to say - i had claude write this piece based on my ideas and ramblings, voice notes, and teachings. the concepts were mine but the words themselves aren’t. i want to say that because its important for me to distinguish, as a writer, what is written ‘by me’ and what’s not. maybe that idea will seem insane and antiquated in a year, i’m not sure, but for now it helps me feel okay about putting stuff out there like this that a) i know is helpful and b) is not MY voice but exists within the umbrella of my business and work. If you have any thoughts or musings on this, i’d genuinely love to hear them - its an open question, all of this stuff, and my guess is as good as yours.

Ready to build this yourself?

Join the next cohort of Code for Creatives

Join the Next Cohort →