Skip to content

Prd 6236#2719

Draft
dimaMachina wants to merge 216 commits intoprd-6192-333from
prd-6236
Draft

Prd 6236#2719
dimaMachina wants to merge 216 commits intoprd-6192-333from
prd-6236

Conversation

@dimaMachina
Copy link
Collaborator

No description provided.

@vercel
Copy link

vercel bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 22, 2026 9:24pm
agents-docs Ready Ready Preview, Comment Mar 22, 2026 9:24pm
agents-manage-ui Ready Ready Preview, Comment Mar 22, 2026 9:24pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 17, 2026

⚠️ No Changeset found

Latest commit: f6251e8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@itoqa
Copy link

itoqa bot commented Mar 22, 2026

Ito Test Report ❌

20 test cases ran. 10 failed, 10 passed.

Overall, the unified run had mixed results (20 cases: 10 passed, 10 failed): core routing and UX checks such as /skills root redirect to first file, /skills/new creation landing on file-routed SKILL.md, breadcrumb behavior, empty-state rendering, mobile edit/save gating, path sanitization for invalid file paths, rename-mismatch rejection, unsaved-changes dialog flows, and agent selector Edit-link navigation all behaved as expected. The most important findings were high-severity regressions introduced by the PR, especially a backend DAL defect with missing imports/undefined symbols (notably SKILL_ENTRY_FILE_PATH plus parseSkillFromMarkdown/SkillFrontmatterSchema/SkillFileApiInsert usage) causing broad 422 failures across skill file create/update/delete and blocking duplicate/conflict, concurrent-create, malformed frontmatter, and XSS validation paths, along with UI issues where create-file submits as native POST instead of client flow, SKILL.md saves can reload stale content due cache/revalidation mismatch, and deleting a skill from SKILL.md leaves users on a broken not-found route.

❌ Failed (10)
Category Summary Screenshot
Adversarial ⚠️ SKILL.md patch fails server-side, blocking XSS-hardening verification. ADV-1
Adversarial ⚠️ Create file endpoint throws SKILL_ENTRY_FILE_PATH ReferenceError and returns generic 422. ADV-2
Adversarial ⚠️ Delete SKILL.md path hits the same runtime error instead of explicit protected-file guard handling. ADV-3
Adversarial ⚠️ Malformed SKILL.md patch is rejected via the same runtime error path, so frontmatter guard logic is bypassed. ADV-4
Adversarial ⚠️ Parallel duplicate create requests both fail because create flow throws before conflict handling runs. ADV-5
Edge ⚠️ File-create flow throws before duplicate-path conflict logic can run. EDGE-3
Edge ⚠️ Concurrent create requests both fail with the same server-side reference error. EDGE-6
Happy-path ⚠️ SKILL.md edits do not reliably persist; refreshed view can show stale pre-save content. ROUTE-4
Happy-path ⚠️ Creating a nested file submits the page as a native form POST and leaves the UI on the new-file route instead of opening the created file route. ROUTE-5
Happy-path 🟠 Deleting a skill from SKILL.md view leaves the app on a deleted file route and shows not found instead of navigating to a valid fallback. ROUTE-8
⚠️ Stored XSS payload does not execute in editor
  • What failed: The patch request fails with a server reference error, so the security behavior cannot be validated because the update path is broken first.
  • Impact: Security validation workflows that depend on updating SKILL.md are blocked, reducing confidence in editor hardening behavior. Normal entry-file updates are also disrupted for users.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open a skill entry file (SKILL.md) in the editor.
    2. Send a patch containing script-tag content to the file update endpoint.
    3. Observe a server reference error instead of the expected save/validation behavior.
  • Code analysis: updateSkillFileById calls buildEntryFileUpdateData, which depends on parseSkillFromMarkdown and SkillFrontmatterSchema symbols that are used but not imported in packages/agents-core/src/data-access/manage/skills.ts.
  • Why this is likely a bug: The entry-file patch flow depends on unresolved symbols in production code, so failure occurs before any XSS-specific validation branch.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 239-245)

function buildEntryFileUpdateData(params: {
  skillId: string;
  files: SkillFileInput[];
  content: string;
}): SkillWriteData {
  const parsed = parseSkillFromMarkdown(params.content);
  const frontmatterResult = SkillFrontmatterSchema.safeParse(parsed.frontmatter);

packages/agents-core/src/utils/skill-files.ts (lines 12-20)

export function parseSkillFromMarkdown(markdown: string): {
  frontmatter: Record<string, unknown>;
  content: string;
} {
  const [frontmatter, content] = simplematter(markdown);
  return {
    frontmatter: frontmatter as Record<string, unknown>,
    content,
  };
}

packages/agents-core/src/validation/schemas/skills.ts (lines 258-269)

SkillFileApiInsertSchema,
  SkillFileApiUpdateSchema,
  SkillFileApiSelectSchema,
  SkillApiUpdateSchema,
  SkillApiSelectSchema,
  SkillFileResponse,
  SubAgentSkillWithIndexArrayResponse,
  SubAgentSkillResponse,
  SkillWithFilesResponse,
  SkillListResponse,
  SkillFrontmatterSchema,
  SkillFileContentInputSchema,
⚠️ API rejects SKILL.md creation through file endpoint
  • What failed: The endpoint returns a generic 422 caused by a runtime ReferenceError, instead of a clean protected-file validation error from the intended SKILL.md guard.
  • Impact: Skill file creation paths are unstable because the handler crashes before business validation runs. API clients receive misleading 422 errors and cannot rely on expected guard semantics.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Create a skill with an existing SKILL.md file.
    2. Call POST /manage/tenants/{tenantId}/projects/{projectId}/skills/{id}/files with {"filePath":"SKILL.md","content":"x"}.
    3. Observe that the response is a generic 422 caused by a runtime ReferenceError.
  • Code analysis: I reviewed the DAL and route handler for skill file creation. The guard compares against SKILL_ENTRY_FILE_PATH, but that identifier is never imported in the DAL file, and the route maps thrown errors to 422.
  • Why this is likely a bug: The create guard dereferences an undeclared identifier in production code, which deterministically throws and prevents intended validation logic from executing.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';
import { agentScopedWhere, projectScopedWhere, subAgentScopedWhere } from './scope-helpers';

packages/agents-core/src/data-access/manage/skills.ts (lines 209-218)

if (!skill) {
    return null;
  }

  if (params.data.filePath === SKILL_ENTRY_FILE_PATH) {
    throw new Error(`Use the skill update flow to manage ${SKILL_ENTRY_FILE_PATH}`);
  }

  if (skill.files.some((file) => file.filePath === params.data.filePath)) {
    throw new Error(`Skill file already exists at path "${params.data.filePath}"`);
  }

agents-api/src/domains/manage/routes/skills.ts (lines 136-140)

if (error instanceof Error) {
      throw createApiError({
        code: error.message.includes('already exists') ? 'conflict' : 'unprocessable_entity',
        message: error.message,
      });
⚠️ API rejects SKILL.md deletion through file endpoint
  • What failed: The request fails via the same runtime ReferenceError path, so the API does not reliably execute the explicit SKILL.md delete guard logic.
  • Impact: Protected-file delete behavior currently depends on an unintended runtime crash instead of stable validation logic. This degrades API correctness and makes error handling brittle for clients.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Fetch skill details and capture the file ID of SKILL.md.
    2. Call DELETE /manage/tenants/{tenantId}/projects/{projectId}/skills/{id}/files/{fileId} using that SKILL.md file ID.
    3. Observe a 422 response from the runtime error path and re-fetch to confirm SKILL.md still exists.
  • Code analysis: I inspected the delete DAL path and route-level error mapping. Delete logic checks SKILL_ENTRY_FILE_PATH using the same undeclared identifier pattern, and route code converts thrown errors to unprocessable responses.
  • Why this is likely a bug: The delete code path references an undeclared constant and throws before intended protected-file logic can run as designed.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';
import { agentScopedWhere, projectScopedWhere, subAgentScopedWhere } from './scope-helpers';

packages/agents-core/src/data-access/manage/skills.ts (lines 335-341)

if (!existingFile) {
    return null;
  }

  if (existingFile.filePath === SKILL_ENTRY_FILE_PATH) {
    throw new Error('Use the skill delete flow to remove SKILL.md');
  }

agents-api/src/domains/manage/routes/skills.ts (lines 136-140)

if (error instanceof Error) {
      throw createApiError({
        code: error.message.includes('already exists') ? 'conflict' : 'unprocessable_entity',
        message: error.message,
      });
⚠️ Malformed SKILL.md frontmatter rejected on patch
  • What failed: The patch rejection occurs through the same runtime ReferenceError branch, so malformed frontmatter validation is not reached through the intended code path.
  • Impact: SKILL.md update validation is unreliable because requests can fail before frontmatter parsing and schema checks execute. This blocks accurate error semantics for API callers.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Fetch SKILL.md file ID for an existing skill.
    2. Call PATCH /manage/tenants/{tenantId}/projects/{projectId}/skills/{id}/files/{fileId} with malformed SKILL.md frontmatter content.
    3. Observe that rejection is produced by the runtime error path before intended frontmatter validation flow executes.
  • Code analysis: I checked the update-file DAL branch that routes SKILL.md updates into frontmatter validation. The branch condition itself uses undeclared SKILL_ENTRY_FILE_PATH, causing failure before buildEntryFileUpdateData is evaluated.
  • Why this is likely a bug: The SKILL.md update branch cannot safely execute because its branch predicate references an undeclared symbol in production DAL code.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';
import { agentScopedWhere, projectScopedWhere, subAgentScopedWhere } from './scope-helpers';

packages/agents-core/src/data-access/manage/skills.ts (lines 291-299)

const data =
    existingFile.filePath === SKILL_ENTRY_FILE_PATH
      ? buildEntryFileUpdateData({
          skillId: params.skillId,
          files,
          content: params.content,
        })
      : { files };

agents-api/src/domains/manage/routes/skills.ts (lines 136-140)

if (error instanceof Error) {
      throw createApiError({
        code: error.message.includes('already exists') ? 'conflict' : 'unprocessable_entity',
        message: error.message,
      });
⚠️ Parallel duplicate create requests preserve uniqueness
  • What failed: Both requests return 422 because execution throws before duplicate-path conflict handling, so expected one-success/one-conflict behavior never occurs.
  • Impact: Concurrent file creation is fully blocked rather than conflict-managed, breaking normal create flow and race-condition guarantees. Automation and UI clients cannot create any skill file through this endpoint while the defect is present.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Pick a unique non-SKILL file path for an existing skill.
    2. Send two concurrent POST /manage/tenants/{tenantId}/projects/{projectId}/skills/{id}/files requests for that same path.
    3. Observe that both requests fail with 422 and conflict semantics are never reached.
  • Code analysis: I reviewed the create-file DAL flow used by both parallel requests. The first comparison to SKILL_ENTRY_FILE_PATH throws due missing import, so neither request reaches duplicate detection logic.
  • Why this is likely a bug: The create endpoint crashes on an undeclared constant before path conflict logic, so parallel duplicate semantics cannot execute at all.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';
import { agentScopedWhere, projectScopedWhere, subAgentScopedWhere } from './scope-helpers';

packages/agents-core/src/data-access/manage/skills.ts (lines 209-219)

if (!skill) {
    return null;
  }

  if (params.data.filePath === SKILL_ENTRY_FILE_PATH) {
    throw new Error(`Use the skill update flow to manage ${SKILL_ENTRY_FILE_PATH}`);
  }

  if (skill.files.some((file) => file.filePath === params.data.filePath)) {
    throw new Error(`Skill file already exists at path "${params.data.filePath}"`);
  }

agents-api/src/domains/manage/routes/skills.ts (lines 136-140)

if (error instanceof Error) {
      throw createApiError({
        code: error.message.includes('already exists') ? 'conflict' : 'unprocessable_entity',
        message: error.message,
      });
⚠️ Duplicate file path conflict handling
  • What failed: Instead of reaching duplicate-path conflict handling, the request path crashes with a 422 server error from undefined symbols.
  • Impact: Core skill file creation and conflict handling are unreliable, so users cannot depend on nested-file workflows. Multiple higher-level editor behaviors are blocked behind this API failure.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open a skill in the Skills editor and create reference/safety-checklist.txt.
    2. Submit another create request for the same reference/safety-checklist.txt path.
    3. Observe the response error instead of normal duplicate-path conflict handling.
  • Code analysis: packages/agents-core/src/data-access/manage/skills.ts uses SkillFileApiInsert, SKILL_ENTRY_FILE_PATH, parseSkillFromMarkdown, and SkillFrontmatterSchema but does not import them; these symbols are defined/exported elsewhere (utils/skill-files.ts, validation/schemas/skills.ts), which supports a concrete runtime/compile-time defect in production code.
  • Why this is likely a bug: The failing code path references symbols that are not imported in the module, so duplicate-path handling cannot execute as designed.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';

packages/agents-core/src/data-access/manage/skills.ts (lines 197-245)

export const createSkillFileById =
  (db: AgentsManageDatabaseClient) =>
  async (params: {
    scopes: ProjectScopeConfig;
    skillId: string;
    data: SkillFileApiInsert;
  }): Promise<SkillFileSelect | null> => {
    const skill = await getSkillByIdWithFiles(db)({
      scopes: params.scopes,
      skillId: params.skillId,
    });

    if (!skill) {
      return null;
    }

    if (params.data.filePath === SKILL_ENTRY_FILE_PATH) {
      throw new Error(`Use the skill update flow to manage ${SKILL_ENTRY_FILE_PATH}`);
    }

function buildEntryFileUpdateData(params: {
  skillId: string;
  files: SkillFileInput[];
  content: string;
}): SkillWriteData {
  const parsed = parseSkillFromMarkdown(params.content);
  const frontmatterResult = SkillFrontmatterSchema.safeParse(parsed.frontmatter);

packages/agents-core/src/utils/skill-files.ts (lines 5-13)

export const SKILL_ENTRY_FILE_PATH = 'SKILL.md';

export interface SkillFileInput {
  filePath: string;
  content: string;
}

export function parseSkillFromMarkdown(markdown: string): {
  frontmatter: Record<string, unknown>;
  content: string;
} {
⚠️ Rapid double-submit on Create file
  • What failed: Expected one success and one conflict, but both requests fail with the same server reference error before idempotency/conflict logic can be validated.
  • Impact: Race-condition protection for create-file operations cannot be trusted because the endpoint fails before business rules are applied. This blocks verification of data integrity guarantees for concurrent writes.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open a skill and prepare a new file path like rapid-<id>.txt.
    2. Send two near-simultaneous POST /skills/{id}/files requests with the same file path.
    3. Observe both requests failing with the same server reference error.
  • Code analysis: The same undefined-symbol defect in packages/agents-core/src/data-access/manage/skills.ts is hit by this concurrent create path, so both requests fail for the same root cause rather than exercising conflict handling.
  • Why this is likely a bug: Concurrent create requests fail due missing imported symbols in production code, not because of test-only mocks or setup changes.

Relevant code:

packages/agents-core/src/data-access/manage/skills.ts (lines 1-20)

import { generateId } from '@inkeep/agents-core';
import { and, asc, count, desc, eq, inArray } from 'drizzle-orm';
import type { AgentsManageDatabaseClient } from '../../db/manage/manage-client';
import { skillFiles, skills, subAgentSkills } from '../../db/manage/manage-schema';
import type {
  SkillFileSelect,
  SkillInsert,
  SkillSelect,
  SubAgentSkillInsert,
  SubAgentSkillWithIndex,
} from '../../types/entities';
import type {
  AgentScopeConfig,
  PaginationConfig,
  ProjectScopeConfig,
  SubAgentScopeConfig,
} from '../../types/utility';
import { getLogger } from '../../utils/logger';
import type { SkillFileInput } from '../../utils/skill-files';

packages/agents-core/src/data-access/manage/skills.ts (lines 197-214)

export const createSkillFileById =
  (db: AgentsManageDatabaseClient) =>
  async (params: {
    scopes: ProjectScopeConfig;
    skillId: string;
    data: SkillFileApiInsert;
  }): Promise<SkillFileSelect | null> => {
    const skill = await getSkillByIdWithFiles(db)({
      scopes: params.scopes,
      skillId: params.skillId,
    });

    if (!skill) {
      return null;
    }

    if (params.data.filePath === SKILL_ENTRY_FILE_PATH) {
      throw new Error(`Use the skill update flow to manage ${SKILL_ENTRY_FILE_PATH}`);
    }

packages/agents-core/src/validation/schemas/skills.ts (lines 242-269)

export {
  SkillApiInsertSchema,
  SkillIndexSchema,
  SubAgentSkillWithIndexSchema,
  SubAgentSkillUpdateSchema,
  SubAgentSkillSelectSchema,
  SubAgentSkillInsertSchema,
  SubAgentSkillApiUpdateSchema,
  SubAgentSkillApiSelectSchema,
  SubAgentSkillApiInsertSchema,
  SkillWithFilesApiSelectSchema,
  SkillUpdateSchema,
  SkillSelectSchema,
  SkillInsertSchema,
  SkillFileSelectSchema,
  SkillFileInsertSchema,
  SkillFileApiInsertSchema,
  SkillFileApiUpdateSchema,
  SkillFileApiSelectSchema,
  SkillApiUpdateSchema,
  SkillApiSelectSchema,
  SkillFileResponse,
  SubAgentSkillWithIndexArrayResponse,
  SubAgentSkillResponse,
  SkillWithFilesResponse,
  SkillListResponse,
  SkillFrontmatterSchema,
  SkillFileContentInputSchema,
};
⚠️ SKILL.md edits revert after refresh in file-routed editor
  • What failed: The editor can revert to older SKILL.md content after refresh even though save was attempted, so users do not see the newly saved text reliably.
  • Impact: Skill authors can lose confidence in edit persistence and may unknowingly operate on stale prompt content. This can cause incorrect skill behavior in downstream agent runs.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open a skill SKILL.md in the file-routed editor.
    2. Edit frontmatter and body content, then click Save changes.
    3. Refresh and reopen the same skill file route.
    4. Observe that older SKILL.md content can reappear instead of the just-saved content.
  • Code analysis: The file-routed skills data path adds React cache(...) wrappers around both the page-level data resolver and per-skill fetcher, and those cached values are reused when rendering file pages. The save path calls revalidation, but this does not clear the React cache memoization used by these wrappers, which plausibly explains stale SKILL.md content after refresh. The run change log shows only dev-server config tweaks and no test-only mocking/bypass of skill save logic.
  • Why this is likely a bug: Production code introduces persistent memoization on the SKILL.md fetch path without a matching cache-bust mechanism, which directly aligns with stale content after save/refresh.

Relevant code:

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/skills/skills-data.ts (lines 11-38)

async function $fetchSkillsPageData(tenantId: string, projectId: string) {
  const [permissions, skillsResponse] = await Promise.all([
    fetchProjectPermissions(tenantId, projectId),
    fetchSkills(tenantId, projectId),
  ]);
  const skillDetails = await Promise.all(
    skillsResponse.data.map((skill) => fetchSkill(tenantId, projectId, skill.id))
  );
  const files = flattenSkillFiles(skillDetails);
  ...
}

export const fetchSkillsPageData = cache($fetchSkillsPageData);

export async function resolveSkillFilePageData(...) {
  const data = await fetchSkillsPageData(tenantId, projectId);

agents-manage-ui/src/lib/api/skills.ts (lines 36-50)

async function $fetchSkill(
  tenantId: string,
  projectId: string,
  skillId: string
): Promise<SkillDetail> {
  ...
  const response = await makeManagementApiRequest<SingleResponse<SkillDetail>>(
    `tenants/${tenantId}/projects/${projectId}/skills/${skillId}`
  );
  return response.data;
}
export const fetchSkill = cache($fetchSkill);

agents-manage-ui/src/lib/api/skills.ts (lines 93-112)

export async function updateSkillFile(...): Promise<SkillFileApiSelect> {
  ...
  const response = await makeManagementApiRequest<SingleResponse<SkillFileApiSelect>>(
    `tenants/${tenantId}/projects/${projectId}/skills/${skillId}/files/${fileId}`,
    { method: 'PATCH', body: JSON.stringify(file) }
  );
  revalidatePath(`/${tenantId}/projects/${projectId}/skills`);
  return response.data;
}
⚠️ Create file form submits with native POST instead of client save
  • What failed: The app stays on the new-file route and navigates with a filePath query string after submit instead of completing client-side creation and routing to /skills/files/... for the new file.
  • Impact: Users cannot create nested files through the primary UI flow, and dependent behaviors (like deep-linking and delete-active-file flows) become blocked. This breaks a core part of the new file-routed skills experience.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Navigate to the Skills page and open a skill from the tree.
    2. Use the context menu to choose Add file.
    3. Enter templates/day/itinerary-card.html and add file content.
    4. Click Create file and observe the post-submit navigation behavior.
  • Code analysis: I inspected the new file editor and routing utilities. The create form binds onSubmit={handleSave}, but handleSave never receives or handles the submit event, so default browser form submission is not prevented; this matches the observed navigation to a query-string URL instead of staying in client-side action flow.
  • Why this is likely a bug: The submit handler is wired without preventing default form submission, so the browser navigates instead of reliably executing the intended client-side create action and route transition.

Relevant code:

agents-manage-ui/src/components/skills/skill-file-editor.tsx (lines 90-136)

const handleSave = async (): Promise<boolean> => {
  if (!canEdit || (!isCreateMode && !isDirty)) {
    return true;
  }

  let didSave = false;

  await form.handleSubmit(async (data) => {
    const nextFilePath = isCreateMode
      ? buildCreateFilePath(createDirectoryPath, data.filePath)
      : data.filePath;

    try {
      const result = isCreateMode
        ? await createSkillFileAction(tenantId, projectId, skillId, nextFilePath, data.content)
        : await updateSkillFileAction(
            tenantId,
            projectId,
            skillId,
            fileId,
            filePath,
            data.content
          );

      if (!result.success) {
        toast.error(result.error ?? `Failed to ${isCreateMode ? 'create' : 'update'} skill file`);
        return;
      }

      const savedFilePath = result.data?.filePath ?? nextFilePath;
      toast.success(isCreateMode ? `Created ${savedFilePath}` : `Saved ${filePath}`);
    } catch {}
  })();

  return didSave;
};

agents-manage-ui/src/components/skills/skill-file-editor.tsx (lines 139-141)

<Form {...form}>
  <form className="contents" onSubmit={handleSave}>
🟠 Deleting skill from SKILL.md view leaves user on invalid route
  • What failed: After successful deletion, the UI stays on the deleted file route and renders a not found state instead of redirecting to /skills (or another valid file route).
  • Impact: Users hit a dead-end view immediately after deleting a skill and lose workflow continuity. This creates confusion in a destructive flow and requires manual navigation recovery.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Navigate to a skill entry file page in the file-routed Skills UI (/skills/files/.../SKILL.md route token).
    2. Click Delete skill and confirm deletion in the dialog.
    3. Observe that the current route remains the deleted file route and renders a not found page instead of redirecting to /skills or another valid file route.
  • Code analysis: I reviewed the file editor delete flow, the delete confirmation component, and the file route resolver path. The delete modal only redirects when redirectOnDelete is truthy, but the SKILL.md delete path in SkillFileEditor does not pass that prop. With no redirect, the deleted route token no longer resolves and the page intentionally renders not found. code_changes.jsonl only shows dev-server config tweaks (host/HMR/devIndicators), not test-only skill behavior changes.
  • Why this is likely a bug: The production code creates a deterministic no-redirect path for entry-file deletion, which directly leads to an unresolved deleted route and a not found screen.

Relevant code:

agents-manage-ui/src/components/skills/skill-file-editor.tsx (lines 184-199)

{isDeleteOpen &&
  (isEntryFile ? (
    <DeleteSkillConfirmation skillId={skillId} setIsOpen={setIsDeleteOpen} />
  ) : (
    <DeleteSkillFileConfirmation
      skillId={skillId}
      fileId={fileId}
      filePath={filePath}
      redirectPath={buildSkillFileViewHref(

agents-manage-ui/src/components/skills/delete-skill-confirmation.tsx (lines 36-41)

toast.success(`Skill "${skillId}" deleted.`);
setIsOpen(false);
if (redirectOnDelete) {
  router.push(`/${tenantId}/projects/${projectId}/skills`);
}

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/skills/(with-sidebar)/files/[...fileSlug]/page.tsx (lines 13-17)

const { selectedFile } = await resolveSkillFilePageData(tenantId, projectId, fileSlug);

if (!selectedFile) {
  return <FullPageError errorCode="not_found" context="skill file" />;
}
✅ Passed (10)
Category Summary Screenshot
Edge Leading-slash path input was rejected and no file was created. EDGE-1
Edge Traversal-style path segments were rejected and no file was created. EDGE-2
Edge SKILL.md rename mismatch was rejected and did not persist after reload. EDGE-4
Edge Unsaved-changes dialog behavior was correct for both Go back and Discard flows. EDGE-5
Edge In mobile viewport, file editing and save completed successfully with the save action becoming enabled after valid edits and persisting changes. EDGE-7
Happy-path Skills root (/skills) redirects to the first file route with SKILL.md loaded. ROUTE-1
Happy-path Global and in-editor breadcrumbs render correctly, and the project breadcrumb navigates to a valid project route. ROUTE-10
Happy-path The empty project /skills view showed the expected No skills yet. state and exposed the Create skill action for an editable user. ROUTE-2
Happy-path Creating a skill from /skills/new lands on the file-routed view with SKILL.md selected. ROUTE-3
Happy-path Edit from agent skill options opens the expected file-routed skill page without a 404. ROUTE-9

Commit: 0ddc49d

View Full Run


Tell us how we did: Give Ito Feedback

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.

9 participants