Skip to content

fix: read task context from ctx.context, inject authToken, fix prompt auth#26

Open
mattverlaque wants to merge 1 commit intoNousResearch:mainfrom
mattverlaque:fix/ctx-context-and-auth
Open

fix: read task context from ctx.context, inject authToken, fix prompt auth#26
mattverlaque wants to merge 1 commit intoNousResearch:mainfrom
mattverlaque:fix/ctx-context-and-auth

Conversation

@mattverlaque
Copy link
Copy Markdown

Problem

The Hermes adapter has several bugs that prevent it from functioning correctly as a Paperclip agent:

  1. buildPrompt() reads ctx.config instead of ctx.context for task/comment data. ctx.config only contains adapterConfig (model, provider, timeout). The wake context (taskId, commentId, wakeReason) lives in ctx.context. This means every run hits the {{#noTask}} heartbeat path — the agent never knows it was woken for a specific task or comment. The @paperclipai/adapter-claude-local correctly reads from context.taskId etc.

  2. Environment vars section has the same ctx.config bug. PAPERCLIP_TASK_ID is never set. Also missing PAPERCLIP_WAKE_REASON and PAPERCLIP_WAKE_COMMENT_ID.

  3. ctx.authToken is never injected as PAPERCLIP_API_KEY. Paperclip generates a JWT for each agent and passes it via ctx.authToken. The Claude adapter sets this as PAPERCLIP_API_KEY in the child env. The Hermes adapter ignores it, so all API calls are unauthenticated and comments are attributed to the board user instead of the agent.

  4. Quiet mode defaults to true, stripping structured output. The parse-stdout.js UI parser needs the -prefixed tool lines that -Q suppresses. With quiet mode on, Paperclip renders everything as system messages instead of tool cards.

  5. Prompt template curl commands have no auth headers. Even with the env var fixed, the prompt examples don't include -H "Authorization: Bearer $PAPERCLIP_API_KEY". Also adds X-Paperclip-Run-Id to mutations, checkout step, and removes python3 one-liners that cause shell escaping issues.

How it was found

Compared line-by-line against @paperclipai/adapter-claude-local/dist/server/execute.js which handles the same AdapterExecutionContext correctly.

Changes

  • src/server/execute.ts: All 5 fixes above (38 insertions, 21 deletions)

kit-floating pushed a commit to kit-floating/hermes-paperclip-adapter that referenced this pull request Apr 5, 2026
Brings the Hermes adapter closer to feature parity with the claude-local
adapter, addressing multiple open issues and incorporating fixes from
several community PRs.

## Fixes included

### Read wake context from ctx.context, not ctx.config (NousResearch#17, NousResearch#26)
Task ID, comment ID, wake reason, and other wake fields are now read
from ctx.context (the Paperclip wake payload) with fallback to
ctx.config for backward compatibility. Previously these were read from
ctx.config which only contains adapter configuration, not wake context.

### Inject PAPERCLIP_API_KEY from ctx.authToken (NousResearch#2, NousResearch#4, NousResearch#5)
The auth token is now injected into the child process environment as
PAPERCLIP_API_KEY. All curl examples in the default prompt template
include Authorization and X-Paperclip-Run-Id headers. Without this,
agent API calls were unauthenticated and comments were attributed to
the Board instead of the agent.

### Add auth headers to all curl commands in prompt template (NousResearch#5, NousResearch#7)
Every curl example now includes -H "Authorization: Bearer
$PAPERCLIP_API_KEY" and mutating requests include -H
"X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" for proper attribution.

### Inject AGENTS.md instructions into prompt (NousResearch#1, NousResearch#14-related)
Reads instructionsFilePath from adapter config and prepends the file
content to the prompt. This mirrors claude-local's
--append-system-prompt-file behavior. Previously, content written in
the Paperclip Instructions tab was stored on disk but never passed to
the Hermes process.

### Forward onSpawn callback to runChildProcess (NousResearch#23)
Passes ctx.onSpawn through to runChildProcess so Paperclip can record
the child process PID. Without this, runs showed processPid=null and
could be incorrectly reaped as process_lost.

### Emit onMeta invocation metadata (NousResearch#14-related)
Calls ctx.onMeta before spawning the process with adapter type,
command, args, cwd, redacted env, and prompt content. This allows
Paperclip to display execution metadata in the run detail view.

### Fall back to config.command when hermesCommand is missing (NousResearch#24, NousResearch#25)
The UI writes the binary path as "command" but the adapter only read
"hermesCommand". Now falls back to config.command before the default.

### Unwrap legacy secret-ref env var format (NousResearch#27, NousResearch#29)
Handles the legacy wrapped env var format ({ type: "plain", value: "..." })
that older Paperclip versions may pass. The server should resolve these
before calling execute(), but this adds defense-in-depth.

### Pass workspace context as environment variables
Sets PAPERCLIP_WORKSPACE_CWD, PAPERCLIP_WORKSPACE_SOURCE,
PAPERCLIP_WORKSPACE_STRATEGY, PAPERCLIP_WORKSPACE_ID,
PAPERCLIP_WORKSPACE_REPO_URL, PAPERCLIP_WORKSPACE_REPO_REF,
PAPERCLIP_WORKSPACE_BRANCH, PAPERCLIP_WORKSPACE_WORKTREE_PATH,
and AGENT_HOME from ctx.context.paperclipWorkspace.

### Pass additional wake context as environment variables
Sets PAPERCLIP_WAKE_REASON, PAPERCLIP_WAKE_COMMENT_ID,
PAPERCLIP_LINKED_ISSUE_IDS, and PAPERCLIP_WAKE_PAYLOAD_JSON.

### Use workspace CWD from Paperclip context
Prefers ctx.context.paperclipWorkspace.cwd over config.cwd when
workspace isolation is enabled (source !== "agent_home").

### Structured wake prompt via renderPaperclipWakePrompt
When available in adapter-utils, uses the standard Paperclip wake
prompt renderer for structured wake payloads. Falls back gracefully
on older adapter-utils versions.

### Session handoff markdown injection
Includes ctx.context.paperclipSessionHandoffMarkdown in the prompt
when the server rotates sessions.

### Composable prompt construction via joinPromptSections
Prompt is now assembled from discrete sections (instructions, wake
prompt, session handoff, heartbeat template) using joinPromptSections,
matching the claude-local approach.

### Include CWD in session params
Session params now include cwd alongside sessionId, matching
claude-local's session codec contract.

### Full session display ID
sessionDisplayId now returns the full session ID instead of truncating
to 16 characters.

### Proper timeout error reporting
Timed-out runs now set errorMessage and errorCode: "timeout" on the
result, matching other adapters.

### Remove unnecessary (ctx as any) casts
ctx.context and ctx.authToken are first-class fields on
AdapterExecutionContext — the type casts were unnecessary.

## Backward compatibility

All new features degrade gracefully:
- adapter-utils functions that may not exist in older versions are
  dynamically imported with fallback
- Wake context fields fall back to ctx.config for older server versions
- Legacy env var wrapping is handled alongside plain strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kit-floating pushed a commit to kit-floating/hermes-paperclip-adapter that referenced this pull request Apr 6, 2026
Brings the Hermes adapter to near-complete feature parity with the
claude-local and codex-local reference adapters, addressing 6 open
issues and incorporating fixes from 7 community PRs.

## Issues addressed
- NousResearch#2 — Missing PAPERCLIP_API_KEY injection
- NousResearch#5 — Inject auth token and add auth headers to curl workflow
- NousResearch#13 — Can't get assigned issues (ctx.context vs ctx.config)
- NousResearch#14 — Emit transcript/invocation events (onMeta)
- NousResearch#24 — execute.ts ignores adapterConfig.command
- NousResearch#27 — HERMES_HOME env var passed as [object Object]

## Community PRs incorporated
- NousResearch#1 (@fa1k3) — AGENTS.md injection
- NousResearch#4 (@shivasymbl) — Inject PAPERCLIP_API_KEY from ctx.authToken
- NousResearch#17 (@optomachina) — Read wake context from ctx.context
- NousResearch#23 (@lucasproko) — Forward onSpawn callback
- NousResearch#25 (@ScaleLeanChris) — Fall back to config.command
- NousResearch#26 (@mattverlaque) — ctx.context + authToken + prompt auth
- NousResearch#29 (@AnandDN) — Unwrap secret-ref env vars

## Changes

### Prompt template alignment
- Replace 60-line hardcoded DEFAULT_PROMPT_TEMPLATE with the standard
  one-liner used by all other Paperclip adapters. The heartbeat
  workflow, curl examples, and API reference are provided by the
  paperclip skill, not hardcoded into the adapter.
- Add bootstrapPromptTemplate support (first-run only prompt, omitted
  on session resume to reduce token waste).
- Compose prompt via joinPromptSections: instructions + bootstrap +
  wake + session handoff + heartbeat template.
- Inject sessionHandoffMarkdown on session rotation.

### Skills injection
- Read skill entries from config.paperclipRuntimeSkills (provided by
  the Paperclip server), symlink into temp dir, pass via -s flag.
  Replaces the need for manual external_dirs in Hermes profile config.
- Clean up temp skills directory in finally block.

### Instructions tab (AGENTS.md)
- Read config.instructionsFilePath and prepend content to prompt.
  Mirrors claude-local's --append-system-prompt-file behavior.
  Previously, Instructions tab content was stored but never passed
  to the Hermes process.

### Auth and attribution
- Inject ctx.authToken as PAPERCLIP_API_KEY (guarded: only set if
  config.env doesn't already contain an explicit PAPERCLIP_API_KEY).
- Fall back to config.command when hermesCommand is missing.
- Unwrap legacy { type: "plain", value: "..." } env var format.

### Wake context
- Read task/comment/wake fields from ctx.context (wake payload) with
  fallback to ctx.config for backward compatibility.
- Set all PAPERCLIP_* env vars matching claude-local: RUN_ID, TASK_ID,
  WAKE_REASON, WAKE_COMMENT_ID, APPROVAL_ID, APPROVAL_STATUS,
  LINKED_ISSUE_IDS, WAKE_PAYLOAD_JSON, all WORKSPACE_* vars,
  AGENT_HOME, WORKSPACES_JSON, RUNTIME_SERVICE_INTENTS_JSON,
  RUNTIME_SERVICES_JSON, RUNTIME_PRIMARY_URL.
- Use renderPaperclipWakePrompt for structured wake payloads.
- Use workspace CWD from context when workspace isolation is enabled.

### Execution metadata
- Call ctx.onMeta with adapterType, command, cwd, args, redacted env,
  prompt, and context.
- Forward ctx.onSpawn to runChildProcess for PID tracking.
- Validate hermes binary via ensureCommandResolvable before spawning.
- Create workspace directory if missing (ensureAbsoluteDirectory with
  createIfMissing).

### Session management
- Validate session CWD before resume — start fresh if CWD changed.
- Retry with fresh session on unknown session error instead of failing.
- Set clearSession on session retry or max-turns detection.
- Enrich sessionCodec with cwd, workspaceId, repoUrl, repoRef fields
  (matching claude-local/codex-local).

### Result object
- Add biller and billingType fields (inferred from resolved provider).
- Set errorCode: "timeout" on timed-out runs.
- Return full sessionDisplayId instead of truncating to 16 chars.

### Output parsing
- Extract parse.ts module from execute.ts with exported functions:
  parseHermesOutput, cleanResponse, isHermesUnknownSessionError,
  isHermesMaxTurnsResult.
- Re-export parse functions from server/index.ts.

### UI build-config
- Add instructionsFilePath, bootstrapPromptTemplate, env bindings
  (plain + secret_ref), provider, and toolsets to buildHermesConfig.
- Add parseEnvVars and parseEnvBindings helpers.

### Environment test
- Add CWD validation check.
- Add live hello probe (hermes chat -q "Respond with hello" -Q, 45s
  timeout) with profile arg forwarding.

### Documentation
- Update agentConfigurationDoc with new config fields.

### Tests
- Add 31 unit tests for parse module using node:test runner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Lempkey
Copy link
Copy Markdown

Lempkey commented Apr 7, 2026

encountered these issues myself and patched locally, good to see there's an open PR for it. Merge it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants