Skip to content

M2: Gateway feature-flags API + persistence#10165

Merged
noanflaherty merged 3 commits into
feature/skill-feature-flags-gatewayfrom
swarm/skill-flag-gateway/task-2
Feb 27, 2026
Merged

M2: Gateway feature-flags API + persistence#10165
noanflaherty merged 3 commits into
feature/skill-feature-flags-gatewayfrom
swarm/skill-flag-gateway/task-2

Conversation

@noanflaherty
Copy link
Copy Markdown
Contributor

@noanflaherty noanflaherty commented Feb 27, 2026

Add GET /v1/feature-flags and PATCH /v1/feature-flags/:flagKey endpoints to gateway with workspace config file persistence. Atomic writes and key validation included. Part of #10145.


Open with Devin

Co-Authored-By: Claude <noreply@anthropic.com>
@noanflaherty noanflaherty self-assigned this Feb 27, 2026
@noanflaherty
Copy link
Copy Markdown
Contributor Author

Where to focus your review

  • gateway/src/http/routes/feature-flags.ts: New route handlers that read/write the workspace config file. Atomic write pattern (temp file + rename) and key validation regex are worth verifying.
  • gateway/src/index.ts (feature-flags routing): Auth enforcement mirrors the existing /integrations/status pattern. The PATCH route uses URL path regex extraction with decodeURIComponent -- worth confirming no injection risk from the decoded key (the ALLOWED_KEY_RE regex validates it after decoding).

Risk level: Medium -- New write path to the shared workspace config file. Concurrent writes from multiple sources (daemon, gateway) could race, though the atomic rename makes partial corruption unlikely.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

…-flags

- Wrap decodeURIComponent in try/catch in gateway/src/index.ts to return
  400 Bad Request on malformed percent-encoding instead of crashing to
  the global error handler with a misleading 500.

- Refactor readConfigFile to use a discriminated union result type that
  distinguishes "file doesn't exist" (returns empty config) from "file
  exists but can't be parsed" (returns error). The PATCH handler now
  returns 500 with a descriptive message when the config file is
  malformed, preventing silent data loss. The GET handler gracefully
  degrades to empty flags on parse errors (no data loss risk).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noanflaherty
Copy link
Copy Markdown
Contributor Author

@codex review this PR again — the previous issues have been fixed in commit 7d4370b

@noanflaherty
Copy link
Copy Markdown
Contributor Author

@devin review this PR again — the previous issues have been fixed in commit 7d4370b

devin-ai-integration[bot]

This comment was marked as resolved.

@noanflaherty
Copy link
Copy Markdown
Contributor Author

@codex review this PR again — the previous issues have been fixed in commit 3527ebd

@noanflaherty
Copy link
Copy Markdown
Contributor Author

@devin review this PR again — the previous issues have been fixed in commit 3527ebd

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@noanflaherty noanflaherty merged commit 33b6905 into feature/skill-feature-flags-gateway Feb 27, 2026
2 checks passed
@noanflaherty noanflaherty deleted the swarm/skill-flag-gateway/task-2 branch February 27, 2026 00:55
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3527ebd82c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* The skillId segment must be a non-empty string of lowercase alphanumeric chars,
* hyphens, and underscores.
*/
const ALLOWED_KEY_RE = /^skills\.[a-z0-9_-]+\.enabled$/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow dotted skill IDs in flag-key validation

ALLOWED_KEY_RE currently rejects . inside the <skillId> segment, but skill IDs are allowed to contain dots (for example validateManagedSkillId in assistant/src/skills/managed-store.ts permits [a-z0-9._-]*). In that case, valid keys like skills.my.skill.enabled are returned by GET yet cannot be updated via PATCH (400), so users cannot toggle those skills through this API even though the runtime flag lookup (skills.${skillId}.enabled) supports them.

Useful? React with 👍 / 👎.

noanflaherty added a commit that referenced this pull request Feb 27, 2026
* M1: Assistant config + skill feature flag enforcement (#10152)

* feat: add skill feature flags config and enforcement

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: enforce feature flags on included child skills and dynamic prompt section

Add isSkillFeatureEnabled checks in skill_load for child skills in both
the body-loading loop and the loaded_skill marker loop, so flag-OFF child
skills are fully hidden. Also filter hardcoded browser/twitter references
in buildDynamicSkillWorkflowSection through isSkillFeatureEnabled so the
system prompt does not advertise disabled skills.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M2: Gateway feature-flags API + persistence (#10165)

* feat: add gateway feature-flags REST API with config persistence

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: handle malformed URI encoding and config parse errors in feature-flags

- Wrap decodeURIComponent in try/catch in gateway/src/index.ts to return
  400 Bad Request on malformed percent-encoding instead of crashing to
  the global error handler with a misleading 500.

- Refactor readConfigFile to use a discriminated union result type that
  distinguishes "file doesn't exist" (returns empty config) from "file
  exists but can't be parsed" (returns error). The PATCH handler now
  returns 500 with a descriptive message when the config file is
  malformed, preventing silent data loss. The GET handler gracefully
  degrades to empty flags on parse errors (no data loss risk).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: validate parsed config is a plain object in readConfigFile

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M3: Client-only PATCH auth split for feature flags (#10171)

* feat: add client-only PATCH auth split for feature-flags

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: remove rate-limit on 403 and handle identical token edge case

- Remove authRateLimiter.recordFailure() from 403 path to avoid penalizing
  legitimately authenticated clients who used the wrong token type (Issue 1)
- Only record failures on 401 (truly invalid authentication)
- Skip runtime-token rejection when FEATURE_FLAG_TOKEN is identical to
  runtimeBearerToken to support single-token deployments (Issue 2)

Addresses review feedback on PR #10171

---------

Co-authored-by: Claude <noreply@anthropic.com>

* M4: Client token plumbing (macOS/iOS) (#10175)

* feat: add feature-flag client token plumbing for macOS/iOS

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: throw on unavailable transport, always use gateway on macOS, clear stale token

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* docs: add feature-flags architecture documentation (#10181)

Co-authored-by: Claude <noreply@anthropic.com>

* fix: use active transport for macOS remote mode, preserve token on re-pair

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review feedback on final PR #10183

1. [P1] Fix gateway type-check: remove dead `not_found` variant from
   ConfigReadResult union (fixes narrowing bug on `result.detail`), make
   `featureFlagToken` optional in GatewayConfig type.

2. [P1] Add featureFlagToken to pairing responses: runtime now reads
   ~/.vellum/feature-flag-token and includes it in approval responses
   so iOS receives the token during QR pairing.

3. [P1] Fix PATCH auth bypass when tokens equal: loadConfig() now detects
   token collision and regenerates the feature-flag token to ensure the
   auth split is always enforceable.

4. [P2] Gate guardian-verification section on feature flags: system prompt
   skips buildGuardianVerificationRoutingSection() when guardian-verify-setup
   skill flag is OFF.

5. [P2] Fix failing integration test: removed incorrect assertion that
   <available_skills> disappears when only browser/twitter flags are OFF
   (bundled skills remain visible).

6. [P2] Fix macOS local HTTP transport: setFeatureFlag now checks if
   httpTransport.baseURL is localhost before delegating — local HTTP mode
   (localHttpEnabled) falls through to the direct gateway call.

7. [P2] Accept dotted skill IDs in feature-flag keys: updated regex from
   [a-z0-9_-]+ to [a-z0-9][a-z0-9._-]* to match managed skill ID validation.

* Migrate hatch visibility to skill flag and remove deprecated client flags

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

1 participant