Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/memory/MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Shared operational memory

Version-controlled operational notes for working on this repo with Claude Code
(or any agent). Each entry below points to one file holding one fact. These are
the *public, repo-portable* subset of operating knowledge — tooling habits, git
hygiene, unattended-run discipline, and milo-specific architecture that is not
already obvious from the code or CLAUDE.md.

Keep entries here generic and non-sensitive: no credentials, cluster names,
project IDs, or internal-only incident detail.

- [gh issue/PR body via --body-file](gh-issue-body-file.md) — use `--body-file` not `--body "$(cat <<EOF)"` so code fences render
- [GitHub sub-issues via gh GraphQL](github-sub-issues-via-graphql.md) — no native `gh` command; use the `addSubIssue` mutation with the `sub_issues` feature header
- [Pull main on switch](pull-main-on-switch.md) — every checkout of main: `git fetch` + `git pull --ff-only` before acting
- [Unattended-loop permission discipline](unattended-loop-permission-discipline.md) — for a zero-prompt `/loop`: Write tool for files, one allowlisted Bash pattern per call, no heredoc/compounds
- [Activity policies owned by source repos](activity-policies-owned-by-source-repos.md) — milo owns its own ActivityPolicy CRs under `config/services/activity/policies/`, shipped to the control plane via an OCI bundle
31 changes: 31 additions & 0 deletions .claude/memory/activity-policies-owned-by-source-repos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: activity-policies-owned-by-source-repos
description: ActivityPolicy CRs are authored in the repo of the service that owns the resource (milo owns its own under config/services/activity/policies/), shipped to the control plane via an OCI bundle
metadata:
type: project
---

`ActivityPolicy` CRs (`activity.miloapis.com`) are authored in the repo of the
service that **owns the resource**, not in a central deploy repo and not in the
activity service repo. The deploy repo only holds Flux Kustomizations that apply
each service's OCI bundle to the milo control plane, all `dependsOn` the activity
aggregated apiserver being ready (it serves the `activity.miloapis.com` CRDs).

milo owns the policies for its own platform resources (Project, Organization,
IAM, identity, notes, notification):
- Authored in `config/services/activity/policies/`.
- Shipped via the `milo-kustomize-bundles` OCIRepository, path `./services/activity`
(Flux Kustomization `milo-activity-policies`), `targetNamespace: milo-system`.

Other services (billing, network-services-operator) ship their own policies the
same way from their own repos.

To change a milo policy in a live control plane: edit the policy file here,
merge, cut a release; Flux re-applies the CR (overwriting the live object) — no
manual kubectl.

**Guard `responseObject` access.** A rejected request carries no
`responseObject`, so a template that reads it unguarded fails and the event
lands in the activity processor DLQ. The existing iam and resourcemanager
create policies already wrap it in `has(audit.responseObject.metadata.name) ?
... : ...` fallbacks; follow that shape in any new or edited policy.
16 changes: 16 additions & 0 deletions .claude/memory/gh-issue-body-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: gh-issue-body-file
description: Use --body-file not a --body heredoc for gh issue/PR create/edit to avoid backtick mangling
metadata:
type: feedback
---

Use `--body-file <path>` instead of `--body "$(cat <<'EOF'...)"` when creating
or editing GitHub issues or PRs whose body contains markdown code fences.

**Why:** Heredoc backtick escaping produces literal backslashes in the rendered
output — fenced code blocks fail to render.

**How to apply:** Write the body to a file with the Write tool (see
[[unattended-loop-permission-discipline]]), then pass `--body-file`. Works for
`gh issue create`, `gh issue edit`, `gh pr create`, and `gh pr edit`.
33 changes: 33 additions & 0 deletions .claude/memory/github-sub-issues-via-graphql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: github-sub-issues-via-graphql
description: How to create/link/list GitHub sub-issues (parent-child) via gh GraphQL — no native gh command
metadata:
type: reference
---

GitHub sub-issues (parent→child hierarchy) have no native `gh issue` command;
use the GraphQL API with the `GraphQL-Features: sub_issues` header.

Get a node id:
```
gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){issue(number:NUM){id}}}' --jq '.data.repository.issue.id'
```

Link child under parent:
```
gh api graphql -H "GraphQL-Features: sub_issues" \
-f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){subIssue{number}}}' \
-f p="$parent_id" -f c="$child_id"
```

List a parent's sub-issues:
```
gh api graphql -H "GraphQL-Features: sub_issues" \
-f query='query{repository(owner:"OWNER",name:"REPO"){issue(number:NUM){subIssues(first:20){nodes{number title state}}}}}' \
--jq '.data.repository.issue.subIssues.nodes[] | "#\(.number) \(.title)"'
```

Gotchas: `gh` must run inside the repo directory (it shells out to git for the
default repo — fails with "not a git repository" from `/tmp`). Capturing a
`gh issue create` URL via `$(... | tail -1)` silently yields empty if gh errored
to stderr; confirm with `gh issue list`.
16 changes: 16 additions & 0 deletions .claude/memory/pull-main-on-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: pull-main-on-switch
description: Every time you switch to the main branch, git fetch then pull --ff-only before acting
metadata:
type: feedback
---

Every time you check out / switch to `main`, immediately `git fetch origin`
then `git pull --ff-only origin main` before doing anything else on main.

**Why:** local main goes stale fast — image-update automation and merged PRs
land on origin/main constantly, and acting on a stale main causes confusion
(e.g. thinking a merged change is not present, or branching from an old base).

**How to apply:** treat the fetch + ff-only pull as one step that always follows
`git checkout main`, before branching or inspecting state.
25 changes: 25 additions & 0 deletions .claude/memory/unattended-loop-permission-discipline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: unattended-loop-permission-discipline
description: To run a /loop hands-off with zero permission prompts, use the Write tool for files and one allowlisted Bash pattern per call — no heredoc/compounds
metadata:
type: feedback
---

To run a recurring `/loop` fully unattended (no permission prompts for hours),
every tool call must match an allowlisted permission.

**Why:** ad-hoc Bash is what breaks unattended runs. `cat > f <<EOF` heredocs,
`a && b` compounds, and jq pipes do not match exact allowlist entries → each one
prompts and stalls the loop.

**How to apply:**
- Write files (issue bodies, comments, manifests) with the **Write tool**, never
`cat`/heredoc. With `defaultMode: acceptEdits` in `.claude/settings.json`,
Write/Edit auto-approve.
- Keep every Bash call to ONE allowlisted glob pattern; no `&&` compounds (each
segment must independently match an allow entry).
- Pre-add broad patterns to `.claude/settings.local.json` allow list, e.g.
`Bash(jq *)`, `Bash(date *)`, `Bash(gh issue *)`, `Bash(gh pr *)`,
`Bash(git *)`. MCP tools must each be allowlisted by full name.
- Pace with `ScheduleWakeup` (~1800s when idle); comment on the tracking issue
ONLY when state changes, else stay silent to avoid issue spam.
Loading