Runbook for Claude Code sessions running inside a clone of GTAG-Manager. Follow this file as the source of truth when a user asks you to set up a Google Tag Manager workspace.
GTAG-Manager is a local Python CLI (binary: gtm-agent) that drives the Google Tag Manager API. It takes a YAML spec describing the variables, triggers, and tags a workspace should contain, diffs that spec against the live workspace, and applies the changes. Optionally it creates and publishes a new container version. It is operated by you (Claude Code) on behalf of the user.
You are running locally on the user's machine. OAuth tokens never leave this machine. There is no hosted service, no telemetry, no third-party intermediary.
If gtm-agent whoami already works, skip this section. Otherwise help the user complete each step before doing any GTM work.
- Create a Google Cloud project. Open https://console.cloud.google.com/, project picker, New Project, name it
gtag-manager.- Verify: the project picker shows the new project as the active project.
- Enable the Tag Manager API. Direct link (one click): https://console.cloud.google.com/apis/library/tagmanager.googleapis.com — click Enable. This step is required; without it every API call returns
accessNotConfigured/SERVICE_DISABLED. Ifgtm-agent listfails with that error after the user thinks they enabled it, ask them to confirm the enabled API is on the same project whose OAuth client they used for.env.- Verify: the API page shows "API enabled" and a "Manage" button.
- Create an OAuth client (Desktop app). APIs & Services > Credentials > Create credentials > OAuth client ID > Desktop app. Copy the Client ID and Client secret.
- Verify: the credentials page lists a new OAuth 2.0 Client ID of type "Desktop".
- Set up
.env.cp .env.example .env # Edit .env and paste GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET.- Verify:
grep GOOGLE_OAUTH .envshows both values populated, neither blank.
- Verify:
- Install the CLI.
pip install -e .- Verify:
gtm-agent --helplists the seven commands (auth,whoami,list,init,plan,apply,publish).
- Verify:
- Authenticate.
gtm-agent auth
- Verify: a browser window opens, the user approves consent, the CLI prints a success message.
- Sanity check.
gtm-agent whoami
- Verify: prints the user's email and three GTM scopes. Stop and debug if email is missing or scopes are wrong.
Once whoami succeeds, the agent is ready. Do not proceed to GTM operations until then.
Decide which mode applies and route accordingly.
(a) "Here is a brief / doc / paragraph describing what we want." The user pastes free-form requirements. Go to section (4) Brief to spec workflow.
(b) "Here is an existing YAML spec at PATH." The user already has a spec file. Run:
gtm-agent plan PATHShow the user the full plan output. Ask: "This will make N changes. Confirm yes apply this to proceed." Wait for the user's explicit confirmation. Then run:
gtm-agent apply PATHOnly run with --publish --yes-publish if the user explicitly told you to publish. See section (6) Publish gate.
(c) "I'm not sure what I need — ask me questions." Run the interactive starter:
gtm-agent init starter.yamlThe wizard prompts the user through the basics. Once starter.yaml is written, switch to mode (b) with PATH=starter.yaml.
When the user pastes a brief, do not start writing YAML immediately. Ask these clarifying questions, one block at a time, and wait for answers:
- GA4 measurement ID. "What is your GA4 measurement ID? It looks like
G-XXXXXXX." If they give aUA-ID, stop and tell them GA4 only. - Events to track. "List every event you want fired, one per line. For each, say whether it is a built-in browser event (page view, link click) or a custom data layer event." Get an explicit list.
- Ecommerce. "Is this an ecommerce site? (yes/no)" If yes: "Which ecommerce events specifically —
purchase,add_to_cart,view_item,begin_checkout, others?" - Consent mode. "Is consent mode required? (yes/no)" If yes: "Default deny analytics + ads, or default deny ads only?"
- App architecture. "Is this a single-page app (Next.js/React/Vue with client-side routing) or a multi-page app (server-rendered, full page loads)?" SPAs need history-change triggers; MPAs do not.
- Page URL scope. "Should tags fire on all pages, or only specific URL patterns? If patterns, list them (e.g.
/checkout/*,/blog/*)."
When all six are answered, generate the spec by editing a copy of an existing example, not from scratch:
- For pageview-only or simple event tracking, copy
examples/ga4-pageview.yaml. - For ecommerce, copy
examples/ga4-ecommerce.yaml.
Write the result to ./generated-spec.yaml. Then validate it:
gtm-agent plan ./generated-spec.yamlIf plan fails with a Pydantic error, the spec is malformed — fix it, do not skip the validation. Show the user the plan output and switch to mode (b) for confirmation and apply.
Never run apply without first showing the user the output of plan and getting an explicit "yes apply this" (or equivalent — "go", "ship it", "do it"). The plan output is the source of truth: every action, every resource, in the order it will run.
If the user is confused by what the plan says, do not proceed. Walk them through each row. If they want changes to the spec, edit the YAML, re-plan, re-confirm.
gtm-agent apply mutates a real GTM workspace. There is no undo button.
Never publish unless the user explicitly says "publish it", "ship it", "push it live", or unmistakable equivalent. The default after a successful apply is to leave the workspace dirty and unpublished — the user can then review the diff in the GTM UI.
The CLI enforces this with the --yes-publish flag: --publish alone is rejected. You must still ask the user before passing both flags. Required exchange:
Agent: "Apply succeeded. The workspace has changes that are not yet published. Want me to publish a new version now?" User: "Yes, publish it."
Then run:
gtm-agent apply ./generated-spec.yaml --publish --yes-publish \
--version-name "<short name>" --version-notes "<what changed and why>"If the user says "not yet" or anything ambiguous, stop. Do not pass --publish.
The YAML schema is a thin wrapper over the GTM v2 API. When you need to know what a type value means, consult these references:
- Tags: https://developers.google.com/tag-platform/tag-manager/api/v2/reference/accounts/containers/workspaces/tags#resource
- Triggers: https://developers.google.com/tag-platform/tag-manager/api/v2/reference/accounts/containers/workspaces/triggers#resource
- Variables: https://developers.google.com/tag-platform/tag-manager/api/v2/reference/accounts/containers/workspaces/variables#resource
type values in the spec are short codes from the API, not display names. Common ones:
- Tags:
gaawc(GA4 config / Google Tag),gaawe(GA4 event),html(custom HTML). - Triggers:
pageview,customEvent,click,linkClick,historyChange,formSubmission. - Variables:
c(constant),v(data layer variable),jsm(custom JavaScript),k(1st party cookie),u(URL).
If you can't find a type code in the API reference, ask the user — do not guess.
- Built-in variables are separate from user-defined variables.
pageUrl,clickElement,clickClasses, etc. are not created via the variables endpoint; they are enabled viabuilt_in_variables.create. The CLI handles this for you, but only if you list them in the spec'sbuilt_in_variablessection. Forgetting this means{{Page URL}}references will silently resolve to empty strings in the browser. - Custom event triggers match the data layer
eventkey. The GTM API field is namedcustomEventFilter(notfilter). The filter compares{{_event}}to the event name string. The CLI's spec usescustom_event_filterin YAML and translates tocustomEventFilteron the wire. - GA4 has two tag types.
gaawcis the Google Tag config (one per page, sets up the gtag context).gaaweis an individual event tag (one per event you fire). You typically need exactly onegaawcplus onegaaweper event. Do not put GA4 measurement ID directly on everygaawe— reference the constant variable that thegaawcalready uses. - Updating existing resources requires
--allow-update. Without it,applyraises on the first 409 conflict and stops. Pass--allow-updateonly when you intend to modify resources that already exist by name. - Variable references inside parameter values use
{{Variable Name}}template syntax. These are strings, resolved at runtime in the browser, not API IDs. The planner does not validate them. The applier emits a warning (not an error) if a referenced name is not present in the workspace — read the warning and fix the spec.
When a command fails:
- Read the log. Tail
./gtm-agent.logto see the full request, response, and stack trace:cat gtm-agent.log | tail -50 - Surface the API error verbatim. Show the user the actual
googleapiclient.errors.HttpErrorreason —userRateLimitExceeded,dailyLimitExceeded,permissionDenied, etc. Do not paraphrase. - Do not auto-retry destructive operations. If
applyfails partway through, do not re-run it without re-runningplanfirst to see the new state. The workspace is now in a partially-updated state and the spec might no longer match what you expect. - Do not retry
dailyLimitExceeded. That error means the project has hit its 10K/day GTM API quota. It resets at midnight Pacific. Tell the user, do not retry. - For OAuth errors, suggest
gtm-agent auth --revokefollowed bygtm-agent auth. Stale or revoked tokens are the most common cause of401 unauthenticated.