Scheduler Agent Management Guide
Monitor the automation runners (gissue schedulers and similar background agents) registered per project (CSN): online/offline status, run status and history, and live logs โ all from one admin screen.
๐ค Let AI Build a Gissue Scheduler
Give this document to an AI, and it will clone or update the giip-fde-agent repository (https://github.com/LowyShin/giip-fde-agent.git), then automatically install, register, and verify a gissue processing scheduler on this PC. Don't write scripts from scratch โ use this procedure instead.
Prompt (copy and use as-is):
Read this document (/help/catquest-schedulers.en.md) and install the gissue scheduler on this PC following the "Installation Procedure" below.
If giip-fde-agent doesn't exist, clone it; if it does, run git pull --ff-only to update. If there are local changes, do not overwrite โ stop and report.
Target CSN is <CSN>, working folder is <path>.
Read the sk= value from giipAgent.cnf (or giipAgent.cfg) and do not ask the user.
Report the result of each step.
Installation Procedure
- Pre-flight checks โ Run:
git --version,node --version,claude --version; for Windows runpowershell -Command $PSVersionTable.PSVersion, for Linux runpwsh --version. All must be in PATH before proceeding. - Get giip-fde-agent โ Run: if missing,
git clone https://github.com/LowyShin/giip-fde-agent.git; if present,git pull --ff-only. If there are local changes, stop and report. - Read the canonical spec โ Read
docs/60-operations/hourly-issue-scheduler.mdยง4~ยง5 to check CSNโworkdir mapping, heartbeat block, and SK location. - Write csn-projects.json โ Copy
scripts/gissue/csn-projects.json.exampletocsn-projects.json. Fill inworkdir/enabled: truefor the target CSN, and in the top-levelheartbeatblock setlssn(server number)/hostname(this PC's identifier)/skFile(sk= format file path) โ all required. If missing, this screen will not show a row for this entry. - Prepare the secret โ Read the
sk=value from the parent folder'sgiipAgent.cnf(orgiipAgent.cfg). Do not ask the user and do not log the value. Also prepareslack-bot/.secrets/giip-accounts.json(copy from .sample.json). - Dry run โ Run
scripts/gissue/manage-gissue-scheduler.ps1 -Action DryRun -OnlyCsn <CSN>โ success if it ends with no[PREFLIGHT-FAIL]logs. - Register
- Windows โ
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\register-hourly-issue-scheduler.ps1 -Action Register -RepoRoot <giip-fde-agent root> - Linux โ No dedicated registration script; register with a single cron line like
/etc/cron.d/gissue-scheduler(same format as actual docker deployment):7,27,47 * * * * <regular user> cd <root> && pwsh -NoProfile -NonInteractive -File "<root>/scripts/gissue/run-gissue-claude.ps1" -OnlyCsn <CSN> >> <root>/scripts/gissue/logs/cron.log 2>&1. Must register as a regular user, not root (claude refuses--dangerously-skip-permissionswhen running as root, causing all issues to fail). That user's claude login credentials are also required. - Docker instance โ On container startup,
docker/entrypoint.shauto-registers the above cron (GIIP_ENABLE_SCHEDULERdefaults to true,-OnlyCsnis derived fromcsn-projects.json). In this case step 7 is already done โ just verify/etc/cron.d/gissue-schedulerexists and check the environment (pwsh symlinks etc.) withscripts/bootstrap-instance.sh.
- Windows โ
- Verify โ Run
manage-gissue-scheduler.ps1 -Action RunOnce -OnlyCsn <CSN>once or wait for the next :07, then refresh this screen โ if thegissue_csn<CSN>row is online and History shows an execution, it's successful. If not visible, check the[WARN][SchedulerState-...]lines inscripts/gissue/logs/gissue_csn<CSN>.log.
What the AI Must Not Do
- Do not rewrite the giip-fde-agent runner (
run-gissue-claude.ps1) - Do not write separate code that calls SP directly (the installed runner handles this)
- Do not leave SK values in documents or logs
- Do not modify the giip-fde-agent code itself
๐ Overview
Scheduler Agent Management shows every automation agent registered against the currently selected project (CSN) โ for example gissue's hourly scheduler and interactive Claude Code sessions. Unlike /admin/catquest (which visualizes giip issues as cats), this screen is an operations dashboard for the agents that run automation jobs: is each agent alive, is its current run healthy, and where can I read its logs.
๐จ Status colors
Two independent status signals are shown per agent โ don't confuse them.
Identity status (small dot next to the agent name)
Reflects whether the agent process itself is reachable (tSchedulerAgent.status), independent of any single run.
| Color | Status | Meaning |
|---|---|---|
| ๐ข Green | online | Agent is reachable and reporting in |
| ๐ด Red | error | Agent reported an error state |
| โช Gray (slate-400) | offline | Agent is not currently reachable |
| โช Light gray (slate-300) | unknown | No status reported yet |
Run status (badge in the Status column)
Reflects the most recent scheduler run (currentStatus), plus a stale detector.
| Color | Label shown | Condition | Meaning |
|---|---|---|---|
| ๐ข Green | RUNNING | currentStatus = RUNNING and not stale | Currently executing normally |
| ๐ด Red | STALE | currentStatus = RUNNING and isStale = true | Marked running but heartbeat has gone quiet too long โ looks like a hung/zombie run |
| ๐ Orange | STALE | currentStatus = STALE (explicit) | Backend flagged the run itself as stale |
| โช Gray | ENDED | currentStatus = ENDED | Finished (success or a controlled stop) |
| โช Gray | (raw value) | anything else / no run yet | Unknown / no data |
In the Run History modal (see below), a completed run can also show FAILED in red โ a run that ended with an error, distinct from an ENDED (clean finish) or a stale/timed-out run.
๐ Screen layout
1. Header
- Back arrow (โ) to
/admin/catquest. - Refresh: reloads the agent list for the current CSN.
2. Open Interactive Sessions
A separate card (only shown when at least one exists) listing active claude_interactive_session agents โ i.e. interactive Claude Code sessions currently registered as running on a host. Each row shows the session name, host, last-communication time, and an "Open session" link when a session URL was registered in the agent's capabilities.
3. Agent list (table)
One row per scheduler agent, with:
- Name โ identity-status dot, agent name, OS/agent-type/version badges,
lssn(linked server), a truncated capabilities summary, and the last recorded error (if any). - Host โ
hostIdentifier. - Windows Task โ the Windows Task Scheduler task name that runs this agent, if applicable.
- Project โ project name.
- Active โ ON/OFF badge (
isActive). - Last Comm โ last heartbeat/communication timestamp.
- Status โ the run-status badge described above, plus the current phase text if one is reported.
A stats footer totals Processed / Skipped / Failed counts across all listed agents.
4. Run History
Below the table, a "View History" card lists every agent again; clicking History on a row opens a modal listing that agent's recent runs โ execution mode, status (including FAILED), start/end time, duration, processed/skipped/failed counts, phase, and a summary.
5. Live Log Viewer
A two-pane live tailing view:
- Left (agent โ stream tree): agents are grouped, each expandable to its known log streams (a stream is identified by
streamKey, has astreamType, and a rotation generation number). An agent shows online/offline based on how recently it last communicated. - Right (log stream card): once you pick a stream, its recent log lines are shown and refreshed for live tailing.
โ Adding Another Host
This screen has no manual "add" button or form โ the list is entirely the result of the two automatic registration paths below. Both call the giipApiSk4 endpoint with a Secret Key (SK) issued per CSN (project) to upsert into tSchedulerAgent/tSchedulerAgentRun; without a valid SK for the target CSN the call itself fails with 401 Unauthorized, so knowing this procedure does not let anyone register a fake agent into someone else's project.
โ ๏ธ The SK/AK values below are always placeholders. Never hard-code real values in docs or code โ store issued credentials your own safe way (a secrets file, environment variable, secret manager, etc.).
Common prerequisites
| Item | Description |
|---|---|
| Target CSN | The project number to register the agent under. This page lists agents for the session's current CSN. |
| SK (Secret Key) | A secret key with write (registration) permission for that CSN. Ask your project administrator to issue an SK for that CSN โ it cannot be self-issued from this screen or API. |
| Registration endpoint | https://giipfaw.azurewebsites.net/api/giipApiSk4 (anonymous auth โ no Function key needed; authentication is via the SK token in the request form) |
Register your console on this screen โ copy and paste into the console
If the Claude Code console you are using does not appear under "Open Interactive Sessions" on this screen, paste one of the requests below into the console (Claude Code). The console performs the registration using only the "Method A" specification below (agentKey rule, SP calls), so no extra script or other repository is needed. Replace <CSN> with the project number to register under.
Prerequisite: the SK for the target CSN must exist as
sk=ingiipAgent.cnf(orgiipAgent.cfg) in a parent folder of this PC (if not, ask the project administrator to issue an SK for that CSN). The console reads the SK from that file and never asks you for it.
Request 1 โ Register this open console once
Register this console session I currently have open as an interactive session of CSN <CSN> on the giip scheduler agent screen (/en/admin/catquest/schedulers).
Read "Method A โ Registering an interactive Claude Code session" in this document (/help/catquest-schedulers.en.md) and follow that specification exactly. Assume no separate script such as register-interactive-session.ps1 exists; run the giipApiSk4 calls written in the document directly (HTTP POST, form fields text/token/jsondata).
1. agentKey: build it with the 5 steps of the document's "agentKey generation rule". The source ID is this session's session_id if you know it, otherwise fallback_<COMPUTERNAME>_<PID>. If the COMPUTERNAME environment variable does not exist, use the output of the hostname command.
2. SchedulerAgentUpsert: text is "SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version". jsondata has agentKey, displayName="Claude Interactive Session (<host name>)", hostIdentifier=<host name>, windowsTaskName/projectName/scheduleDesc as empty strings, isActive="1", lssn="0", osType=Windows or Linux, agentType="claude_interactive_session", version="". If the environment variable CLAUDE_CODE_BRIDGE_SESSION_ID exists, add capabilities={"sessionUrl":"https://claude.ai/code/<that value>"} as the last field of both text and jsondata; if it does not exist, leave capabilities out entirely. Never invent a sessionUrl from any other value.
3. SchedulerAgentRunStart: text is "SchedulerAgentRunStart runIdKey agentKey executionMode totalIssueCount". runIdKey=<agentKey>_<current time yyyyMMddHHmmss>, executionMode="interactive", totalIssueCount="0".
4. Read the SK from the sk= value of giipAgent.cnf (or giipAgent.cfg) in a parent folder and use it as token. Do not ask me for it, and never print or record the SK value on screen, in logs or in any file.
5. Verify: call the same giipApiSk4 with text "SchedulerAgentList csn" and jsondata {"csn":"<CSN>"}, and confirm the row for the agentKey you just created has is_active=true and current_status=RUNNING. Tell me the result and the agentKey/runIdKey you used (never the SK).
Request 2 โ Set up hooks so every future session registers automatically
Set up SessionStart/SessionEnd hooks so that every time a Claude Code session opens or closes on this PC, it is automatically registered/unregistered on the giip scheduler agent screen. The target CSN is <CSN>.
Read "Method A โ Registering an interactive Claude Code session" in this document (/help/catquest-schedulers.en.md) and implement it yourself using only that specification. Do not try to fetch a script from another repository.
1. Create a hook handler script in one place under the user's home (e.g. ~/.claude/giip-console-hook.ps1). It takes Start or End as an argument and reads the hook event JSON from standard input. On non-Windows you may write it for pwsh or bash instead.
2. Register the hooks as in the document's JSON: add command hooks to hooks.SessionStart and hooks.SessionEnd in ~/.claude/settings.json (global). Do not add a matcher. Do not overwrite existing hooks entries โ append to the arrays โ and use the timeout value from the document's JSON as is.
3. Exclude headless runs: the script first walks up its parent processes and, if the command line of the claude process contains -p or --print, does nothing and exits (the document's headless check). Do not decide headless by the presence of CLAUDE_CODE_BRIDGE_SESSION_ID.
4. Start: build agentKey by the document's "agentKey generation rule" (source ID = session_id from stdin, otherwise fallback_<COMPUTERNAME>_<PID>), then call SchedulerAgentUpsert (agentType="claude_interactive_session", isActive="1", fields and order identical to the document's example) and SchedulerAgentRunStart (executionMode="interactive", totalIssueCount="0"). runIdKey=<agentKey>_<current time yyyyMMddHHmmss>; save it to a file in the temp folder so End can use it. Include capabilities.sessionUrl only when CLAUDE_CODE_BRIDGE_SESSION_ID exists; if not, continue registering without capabilities.
5. End: recompute agentKey by the same rule, call SchedulerAgentRunEnd (status="ENDED", processedCount/skippedCount/failedCount="0"), then call SchedulerAgentUpsert with isActive="0" to switch it offline.
6. Make every call through giipApiSk4 (text/token/jsondata), and always exit with code 0 so a failing hook never blocks the session.
7. Do not put the SK value in the script; write only the absolute path of giipAgent.cnf (or giipAgent.cfg) and read sk= from it on every run. Do not ask me for it, and never leave the SK value in output, logs or files.
8. When done, tell me the list of files you created or changed (paths) and how to confirm in a new session that it was registered.
- A session registered with Request 1 has no end hook. To unregister it when you close it, ask the console: "Call SchedulerAgentRunEnd with the agentKey/runIdKey you reported, then call SchedulerAgentUpsert with isActive=0." With the hooks (Request 2) it is unregistered automatically on exit.
- Success check: after refreshing the screen, the session appears in the "Open Interactive Sessions" card and the agent list. See "Method A" below for the detailed specification.
Method A โ Registering an interactive Claude Code session
To make an interactive AI coding session (e.g. Claude Code) appear in the "Open Interactive Sessions" card, configure its SessionStart/SessionEnd hooks to run the following procedure.
- Prerequisites: the common prerequisites above (an SK for the target CSN), plus the ability to configure SessionStart/SessionEnd hooks in the tool running the session.
- On session start โ call
SchedulerAgentUpsertto register/refresh identity, thenSchedulerAgentRunStartto record the run start. - On session end โ call
SchedulerAgentRunEndto record the run end, then callSchedulerAgentUpsertagain withisActive=0to mark the agent offline.
agentKey generation rule (apply exactly, no judgment needed): csn + agentKey is a UNIQUE constraint in the database, so the same session must always yield the same value, even across restarts (otherwise a restart inserts a new row instead of updating the existing one). Compute it with these exact steps:
- Source ID: use the hook stdin JSON's
session_idfield if present. If absent, usefallback_<value of the COMPUTERNAME env var>_<current process PID>as the source ID. - Normalize: remove every character from the source ID that is not alphanumeric (
a-z,A-Z,0-9) โ regex replace[^a-zA-Z0-9]with''. - Truncate (1st pass): if the normalized value is longer than 24 characters, keep only the first 24.
- Combine:
agentKey = "claude_interactive_" + <value of the COMPUTERNAME env var> + "_" + <result of step 3> - Truncate (2nd pass): if the step-4 result is longer than 100 characters, keep only the first 100. Use this final value as
agentKey.
Where to register the SessionStart/SessionEnd hooks (paste this JSON as-is): for Claude Code, pick either the project root's .claude/settings.json (applies to that project only) or the user home's ~/.claude/settings.json (applies globally to all projects), then merge the hooks key below into that file's top level. If the file already has a hooks key, add the objects below inside its SessionStart/SessionEnd arrays (do not overwrite existing entries). Do not add a matcher field โ this hook applies globally to every session.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "<command to run on session start. Example: powershell -NoProfile -ExecutionPolicy Bypass -File \"<absolute path to your registration script>\" -Action Start>",
"timeout": 15000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "<command to run on session end. Example: powershell -NoProfile -ExecutionPolicy Bypass -File \"<absolute path to your registration script>\" -Action End>",
"timeout": 15000
}
]
}
]
}
}
โ ๏ธ If you registered this as a global (no-matcher) hook, beware headless misclassification (giip #1837): the hook above has no
matcher, so it fires for every claude process on this host โ not just interactive sessions, but also every headless invocation an unattended scheduler opens withclaude -p. That means your registration script effectively needs logic to decide "is this invocation interactive or headless?" Do not use the presence/absence of$env:CLAUDE_CODE_BRIDGE_SESSION_ID(whether the Remote Control bridge is connected) as that signal โ that variable only reflects bridge-connection status, not headlessness, and a genuinely interactive session that simply hasn't run/remote-controlyet can also lack it (this exact bug has caused real interactive sessions to be misclassified as headless and silently skipped from registration โ giip #1837). The correct way to decide: walk up from this hook's own process to find the actualclaude/claude.exeprocess, and check whether that process's command line includes a-p/function Test-IsHeadlessClaudeInvocation { try { $checkPid = $PID for ($i = 0; $i -lt 6; $i++) { $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$checkPid" -ErrorAction Stop if (-not $proc) { return $false } if ($proc.Name -eq 'claude.exe') { $cmd = $proc.CommandLine if ($cmd -and ($cmd -match '(^|\s)(-p|--print)(\s|$)')) { return $true } return $false } $checkPid = $proc.ParentProcessId if (-not $checkPid -or $checkPid -eq 0) { return $false } } return $false } catch { return $false } }On non-Windows platforms, implement the same principle (check the ancestor process's command line for
-p/
That command receives the hook event JSON (including session_id) on stdin. You can implement this self-containedly using only the SP call spec in this document (the call convention, field list, and agentKey generation rule below) โ even if the operator who maintains this document has a personal reference implementation, that repository is private and unreachable by other sessions, so do not try to fetch code from anywhere; write it yourself from this document's spec alone.
Note that agentKey and capabilities.sessionUrl come from two different IDs โ conflating them can lead you to conclude, incorrectly, that registration is blocked because "there's no session ID to build a URL from":
agentKeyโ built from the hook stdin'ssession_id(the harness's internal UUID, the same value used in~/.claude/projects/*/<uuid>.jsonlfile names). Its only purpose is to make restarts of the same session yield the same key; it is not a URL.capabilities.sessionUrlโ when available, built from$env:CLAUDE_CODE_BRIDGE_SESSION_ID(e.g. a ULID-style value likesession_01JGmSiTajXu9iZmjVZC27Ntโ a different ID from the hook stdin'ssession_id), ashttps://claude.ai/code/<that value>.
This environment variable isn't present in every session โ for example, a local CLI session that never goes through the claude.ai/code bridge may not have it. If $env:CLAUDE_CODE_BRIDGE_SESSION_ID is absent, that just means you can't build a sessionUrl โ it does not mean registration is blocked. In that case, simply drop the capabilities field from the call entirely and proceed with the remaining fields (see "When you can't build a sessionUrl" below).
When CLAUDE_CODE_BRIDGE_SESSION_ID gets set, and whether you can get it after the fact if it's missing (per the official docs at https://code.claude.com/docs/en/remote-control โ a procedure you can follow as-is):
This environment variable exists only when the session is connected to Anthropic's "Remote Control" feature. If any of the conditions below isn't met, it cannot be obtained at all, and that is normal, not a defect โ in that case, don't try anything else; just drop capabilities and continue registration.
- Headless (
claude -p) sessions don't have this feature at all. Remote Control is interactive-session-only, so an unattended process running viaclaude -pโ like the gissue scheduler โ can never have this value. - Sessions authenticated with an API key (
ANTHROPIC_API_KEY) don't support Remote Control. You need to be logged into a claude.ai account (runclaude, then/login). - It isn't supported when going through Amazon Bedrock / Google Vertex / Microsoft Foundry, or when
ANTHROPIC_BASE_URLis set to anything other thanapi.anthropic.com(an LLM gateway, proxy, etc.). - It isn't supported if any of
DISABLE_TELEMETRY,DO_NOT_TRACK,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, orDISABLE_GROWTHBOOKis set (if one of these is set, you'd need to unset it).
If this variable is still missing even though a plain interactive session meets every condition above, Remote Control simply hasn't connected yet. You don't need to restart the session โ running the following command inside it connects immediately, and $env:CLAUDE_CODE_BRIDGE_SESSION_ID is set from that moment on:
/remote-control
(/rc works the same way.) To have it connect automatically from every future session, either run /config inside a session and turn on "Enable Remote Control for all sessions," or add the following to ~/.claude/settings.json in your user home:
{
"remoteControlAtStartup": true
}
Also strictly forbidden here (same principle as the guide's line 147): when you cannot obtain CLAUDE_CODE_BRIDGE_SESSION_ID because one of the conditions above isn't met (headless execution, API-key auth, etc.), do not try to "manufacture" a sessionUrl by substituting some other value, such as the hook stdin's session_id or $env:CLAUDE_CODE_SESSION_ID โ those values have nothing to do with an actual claude.ai/code session and produce a broken/404 link. In that situation the only correct move is to omit capabilities and continue registration โ there is no other workaround to attempt.
This decision is purely mechanical โ never ask the human. Branch only on whether $env:CLAUDE_CODE_BRIDGE_SESSION_ID is present in your own process; never present this as a choice or ask the user which value to use. Never reuse the hook stdin's session_id (the source of agentKey) as sessionUrl โ it's a different ID and produces a broken/404 link. Never substitute sessionUrl with any other value (an internal tool link, a guessed URL, etc.) โ sessionUrl must be either a genuine claude.ai/code bridge link, or omitted entirely.
If you want sessionUrl populated: the session performing this registration must itself be connected to the Remote Control bridge for $env:CLAUDE_CODE_BRIDGE_SESSION_ID to be set. This is a state each session establishes for itself โ it cannot depend on some other operator's machine settings (e.g. a setting that auto-connects the bridge at session start on their PC); another person's PC settings have no effect on this session. If not yet connected, run /remote-control (inside an interactive session) or restart with the claude --remote-control/--rc flag to connect it yourself. Registration itself (Upsert/RunStart) should still proceed while you wait for the connection โ follow the "When you can't build a sessionUrl (omitting capabilities)" section below and don't defer registration entirely.
Call convention (common to giipApiSk4): HTTP POST with 3 application/x-www-form-urlencoded form fields.
โ ๏ธ Positional parameters: these SPs (e.g.
pApiSchedulerAgentUpsertBySK) bind the fields injsondatato the SP's individual positional parameters (@p0, @p1, ...) in order โ middle fields cannot be skipped. To fill a later field (e.g.capabilities) you must also send every field before it, using safe placeholder values for the ones you don't actually use (e.g.windowsTaskName='',projectName='',scheduleDesc='',lssn='0',version=''). Sending an empty string to an integer parameter (lssn, etc.) causes a conversion error, so it must be a numeric string. That said,capabilitiessits at the very end of this field list, so when it has no value (i.e.$env:CLAUDE_CODE_BRIDGE_SESSION_IDis absent, as described above) it's safe to omit the field entirely โ there's nothing after it whose position would shift. The positional constraint only means you can't skip a middle field; it does not mean you must always fill the last one too.
| Form field | Value |
|---|---|
text | "<SPName> <field1> <field2> ..." (SP name without its BySK suffix, followed by the field names this call fills, space-separated) |
token | <YOUR_SECRET_KEY> (the target CSN's SK) |
jsondata | a JSON string mapping field name โ value, e.g. {"agentKey":"...","displayName":"..."} |
Fields actually filled at each step:
| Step | SP name (first token in text) | Key fields |
|---|---|---|
| Start โ register/refresh identity | SchedulerAgentUpsert | agentKey (a unique per-host-per-session identifier), displayName, hostIdentifier (host name), windowsTaskName, projectName, scheduleDesc, isActive=1, lssn, osType, agentType (e.g. claude_interactive_session), version, capabilities (a JSON string, e.g. {"sessionUrl":"<URL to open the session>"}) |
| Start โ start a run | SchedulerAgentRunStart | runIdKey (unique key for this run), agentKey, executionMode=interactive, totalIssueCount |
| End โ end the run | SchedulerAgentRunEnd | runIdKey, agentKey, status=ENDED, processedCount, skippedCount, failedCount |
| End โ mark identity offline | SchedulerAgentUpsert | agentKey, displayName, hostIdentifier, windowsTaskName, projectName, scheduleDesc, isActive=0 |
Example (PowerShell, all values are placeholders):
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version capabilities'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
capabilities = (@{ sessionUrl = '<optional session URL>' } | ConvertTo-Json -Compress)
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
When you can't build a sessionUrl (omitting capabilities): if $env:CLAUDE_CODE_BRIDGE_SESSION_ID is absent so you can't construct capabilities, just drop the capabilities token from text and the capabilities field from jsondata in the example above, and use the other 11 fields as-is. Registration succeeds completely normally without a sessionUrl โ the only difference is that the "Open Interactive Sessions" card won't show an "Open session" link (see the "Troubleshooting" section below).
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
To reproduce this on another host, the following two things are required as-is (both are fixed requirements, with no discretion): (a) the same hooks.SessionStart/hooks.SessionEnd configuration in that host's session-running tool, and (b) a valid SK for the CSN you're registering into.
Where to store the SK is a pure security judgment call unrelated to this procedure โ for reference, this operator's personal environment (the lowyworkenv repository) keeps this SK in a JSON file, slack-bot/.secrets/giip-accounts.json (structured as Slack-channel-ID โ {login_id, sk, csn}), but this is just an example structure โ it is not an instruction to follow this exact file path. Other hosts/operators should store their SK however is safe for them (an OS credential store, a secret manager, an environment variable, etc.).
Confirming success: refresh the screen โ the session appears in the "Open Interactive Sessions" card.
Method B โ Registering a scheduler agent (automation runner)
Registering a background automation runner (like the hourly gissue scheduler) uses the same SP family as Method A, but instead of a one-shot call it periodically (heartbeat) refreshes its own identity and run state.
The standard implementation is giip-fde-agent. Don't write from scratch โ follow the "Let AI Build a Gissue Scheduler" procedure above. The SP specs below are the calls the runner uses internally; reference them only when implementing directly.
- Prerequisites: the common prerequisites above (an SK for the target CSN).
- Initial registration / periodic identity refresh: call
SchedulerAgentUpsert(same fields as Method A โagentKey/displayName/hostIdentifier/osType/agentType/version/capabilities, etc.) once when the process starts, then repeat it at no more than 5-minute intervals (recommended: every 2 minutes). - Start a run:
SchedulerAgentRunStart(runIdKey,agentKey,executionMode=scheduled,totalIssueCount). - Update progress during the run (heartbeat):
SchedulerAgentHeartbeatPutโrunIdKey,agentKey,processedCount,skippedCount,failedCount,currentPhase,currentIssueNum,totalIssueCount. Call it after each unit of work (e.g. after finishing one issue), and even while idle with nothing to process, call it at no more than 2-minute intervals โ if the heartbeat stops for 5 minutes or more, the screen shows the run as STALE. - End the run:
SchedulerAgentRunEnd(runIdKey,agentKey,status,processedCount,skippedCount,failedCount,exitCode,summary).statusvalues areSUCCEEDED/TIMED_OUT/FAILED;exitCodeis a numeric string ('0'or'1').
| SP name | Auth | Purpose |
|---|---|---|
SchedulerAgentUpsert | SK | Register/refresh identity |
SchedulerAgentRunStart | SK | Record run start |
SchedulerAgentHeartbeatPut | SK | Update progress/heartbeat during a run |
SchedulerAgentRunEnd | SK | Record run end |
SchedulerAgentList | AK (logged-in session) | Used by this screen to fetch the list โ not needed for registration |
Positional argument order (Method 1):
- Upsert argument order:
agentKey,displayName,hostIdentifier,windowsTaskName,projectName,scheduleDesc,isActive - RunStart argument order:
runIdKey,agentKey,executionMode,totalIssueCount - HeartbeatPut argument order:
runIdKey,agentKey,processedCount,skippedCount,failedCount,currentPhase,currentIssueNum
Both call conventions are valid. Method 1 (inside the runner): giipApiSk2 with token=SK, text="SPName 'value1' 'value2' ..." (positional arguments in SP parameter declaration order, values as single-quoted literals), jsondata empty. Method 2 (for direct implementation, same format as Method A): giipApiSk4 with token=SK, text containing field names space-separated, jsondata containing {"fieldName":"value",...} JSON.
Note: giipAgentWin/giipAgentLinux (server infrastructure agents managed in this project) use a completely separate registration mechanism (an x-api-key-based
agent-register/agent-heartbeatAPI) unrelated to thistSchedulerAgentsystem and this screen. To register into this screen (admin/catquest/schedulers) you need an implementation that calls the SP family above; the standard implementation for the hourly gissue scheduler isrun-gissue-claude.ps1in giip-fde-agent.
Confirming success: after the next heartbeat/refresh, the agent appears in the agent list table.
๐ก Notes
- This is an admin screen. Access requires
AdminGuardclearance (uLevel โฅ 70). - The agent list, run history, and log catalog are all scoped to the session's current CSN โ switch project from the top navigation to inspect a different project's agents.
- Loading data requires an authenticated session (AK token); if requests start failing with an auth error, log in again and press Refresh.
- This page is a distinct tool from
/admin/catquestโ reaching it via the CatQuest screen's navigation does not mean it shares CatQuest's cat-visualization data or audience.
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| Told an interactive session to register but it never shows up under "Open Interactive Sessions" | Most likely the global hook's headless-detection logic decided based solely on $env:CLAUDE_CODE_BRIDGE_SESSION_ID presence/absence, misclassifying a bridge-unconnected interactive session as headless and skipping registration entirely (giip #1837) | Replace the detection logic with the process-command-line check described in the "beware headless misclassification" box above. |
| "Authentication required" error | No AK/session token found | Log in again, then reload the page. |
| Agent list is empty | No scheduler agents registered for this CSN, or wrong project selected | Confirm the correct project (CSN) is selected in the top navigation. |
| A row shows STALE in red while you know the job is idle | The agent stopped sending heartbeats mid-run (isStale = true) without transitioning to ENDED โ likely a crashed/killed process | Check the agent host directly; once it restarts and reports again, the badge updates. |
| Live Log Viewer shows "No streams yet." | The agent hasn't registered any log streams yet, or hasn't produced output since the catalog last refreshed | Wait for the agent's next run, or confirm the agent process is actually writing logs. |
| "Open session" link missing under Open Interactive Sessions | No sessionUrl was included in that agent's registered capabilities | Nothing to open โ the interactive session didn't register a reachable URL. |
Version: 1.1
Last updated: 2026-09-01
Source file: giipv3/public/help/catquest-schedulers.en.md